Skip to main content
Glama

think-session-manager

Manages structured thinking sessions by listing, searching, retrieving detailed analysis, or resuming previous sessions for complex problem-solving.

Instructions

Manage thinking sessions: list with pagination/filtering, search by content, get detailed analysis, or resume previous sessions

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYes
sessionIdNo
limitNo
offsetNo
queryNo
strategyNo
includeTopicsNo
sortByNotimestamp
completionStatusNo
minScoreNo

Implementation Reference

  • The handler function that implements the core logic of the 'think-session-manager' tool. It processes actions ('list', 'get', 'resume', 'search') by calling methods on the shared sessionStorage instance (ThinkingSessionStorage), handling pagination, filtering, searching, and resuming sessions.
    async (args) => {
      try {
        switch (args.action) {
          case "list":
            const listOptions = {
              limit: args.limit,
              offset: args.offset,
              strategy: args.strategy,
              includeTopics: args.includeTopics,
              sortBy: args.sortBy
            };
            
            const listResult = await sessionStorage.listSessions(listOptions);
            
            return {
              content: [{
                type: "text",
                text: JSON.stringify({
                  action: "list",
                  total: listResult.total,
                  offset: listResult.offset,
                  limit: listResult.limit,
                  hasMore: listResult.hasMore,
                  sessions: listResult.sessions.map(s => ({
                    sessionId: s.id.split('-').slice(-2).join('-'), // Extract timestamp portion  
                    description: s.sessionPurpose || s.topics?.[0]?.term + ' analysis' || 'General thinking session',
                    strategy: s.strategy,
                    quality: s.qualityRating ? Object.values(s.qualityRating).filter(v => typeof v === 'number').reduce((sum, val, _, arr) => sum + val / arr.length, 0).toFixed(1) : null,
                    duration: s.automaticMetrics?.duration || null,
                    thoughtCount: s.thoughtCount,
                    completion: s.completion,
                    complexity: s.complexity,
                    age: s.age
                  }))
                }, null, 2)
              }]
            };
    
          case "get":
            if (!args.sessionId) {
              throw new Error("sessionId required for 'get' action");
            }
            
            // Handle both full session ID and shortened version
            let fullSessionId = args.sessionId;
            if (!args.sessionId.includes('session-')) {
              // If it's a shortened ID, we need to find the full ID
              const allSessions = await sessionStorage.listSessions({ limit: 1000 });
              const matchingSession = allSessions.sessions.find(s => 
                s.id.split('-').slice(-2).join('-') === args.sessionId
              );
              if (matchingSession) {
                fullSessionId = matchingSession.id;
              }
            }
            
            const session = await sessionStorage.getSession(fullSessionId);
            
            if (!session) {
              throw new Error(`Session not found: ${args.sessionId}`);
            }
            
            const sessionAnalysis = {
              id: session.id,
              strategy: session.strategy,
              timestamp: session.timestamp,
              thoughtHistory: session.thoughtHistory.map(t => ({
                thoughtNumber: t.thoughtNumber,
                absoluteNumber: t.absoluteNumber,
                sequenceNumber: t.sequenceNumber,
                thought: t.thought.substring(0, 150) + (t.thought.length > 150 ? '...' : ''),
                stage: t.currentStage,
                hasActions: t.plannedActions ? t.plannedActions.length > 0 : false,
                hasResults: t.actionResults ? t.actionResults.length > 0 : false
              })),
              branches: Object.keys(session.branches),
              topics: sessionStorage.extractTopics(session.thoughtHistory),
              complexity: sessionStorage.calculateComplexity(session.thoughtHistory, session.branches),
              completion: sessionStorage.getCompletionStatus(session.thoughtHistory),
              summary: {
                totalThoughts: session.thoughtHistory.length,
                totalBranches: Object.keys(session.branches).length,
                finalStage: session.thoughtHistory[session.thoughtHistory.length - 1]?.currentStage,
                completed: !session.thoughtHistory[session.thoughtHistory.length - 1]?.nextThoughtNeeded
              }
            };
            
            return {
              content: [{
                type: "text",
                text: JSON.stringify({
                  action: "get",
                  ...sessionAnalysis
                }, null, 2)
              }]
            };
    
          case "search":
            if (!args.query) {
              throw new Error("query required for 'search' action");
            }
            
            const searchOptions = {
              limit: args.limit,
              offset: args.offset,
              minScore: args.minScore,
              strategy: args.strategy,
              completionStatus: args.completionStatus
            };
            
            const searchResult = await sessionStorage.searchSessions(args.query, searchOptions);
            
            return {
              content: [{
                type: "text",
                text: JSON.stringify({
                  action: "search",
                  query: args.query,
                  ...searchResult,
                  sessions: searchResult.sessions.map(s => ({
                    sessionId: s.id.split('-').slice(-2).join('-'), // Extract timestamp portion for shorter ID
                    description: s.sessionPurpose || s.thoughtHistory[0]?.thought.substring(0, 100) + '...' || 'No description available',
                    strategy: s.strategy,
                    quality: s.qualityRating ? Object.values(s.qualityRating).filter(v => typeof v === 'number').reduce((sum, val, _, arr) => sum + val / arr.length, 0).toFixed(1) : null,
                    duration: s.automaticMetrics?.duration || null,
                    completion: s.completion,
                    searchScore: s.searchScore.toFixed(2),
                    matchedTerms: s.matchedTerms,
                    age: `${Math.round((Date.now() - new Date(s.timestamp)) / (1000 * 60))}m ago`
                  }))
                }, null, 2)
              }]
            };
    
          case "resume":
            if (!args.sessionId) {
              throw new Error("sessionId required for 'resume' action");
            }
            
            // Handle both full session ID and shortened version
            let fullResumeSessionId = args.sessionId;
            if (!args.sessionId.includes('session-')) {
              // If it's a shortened ID, we need to find the full ID
              const allSessions = await sessionStorage.listSessions({ limit: 1000 });
              const matchingSession = allSessions.sessions.find(s => 
                s.id.split('-').slice(-2).join('-') === args.sessionId
              );
              if (matchingSession) {
                fullResumeSessionId = matchingSession.id;
              }
            }
            
            const resumeSession = await sessionStorage.getSession(fullResumeSessionId);
            
            if (!resumeSession) {
              throw new Error(`Session not found: ${args.sessionId}`);
            }
            
            // Restore session state
            thinkingServer.thoughtHistory = resumeSession.thoughtHistory;
            thinkingServer.branches = resumeSession.branches;
            thinkingServer.sessionId = resumeSession.id;
            thinkingServer.strategy = resumeSession.strategy;
            thinkingServer.stageManager = new StageManager(resumeSession.strategy);
            thinkingServer.isInitialized = true;
            
            // Reconstruct dual numbering from history
            const lastThought = resumeSession.thoughtHistory[resumeSession.thoughtHistory.length - 1];
            thinkingServer.absoluteThoughtNumber = lastThought?.absoluteNumber || 0;
            thinkingServer.sequenceThoughtNumber = lastThought?.sequenceNumber || 0;
            
            // Set stage to last known stage
            if (lastThought?.currentStage) {
              thinkingServer.stageManager.currentStage = lastThought.currentStage;
            }
            
            console.error(`📚 Resumed session: ${resumeSession.id} (${resumeSession.strategy})`);
            
            // Build semantic response for current state
            const semanticResponse = thinkingServer.parameterRouter.buildSemanticResponse(
              thinkingServer.strategy,
              thinkingServer.stageManager.getCurrentStage(),
              thinkingServer.thoughtHistory,
              thinkingServer.sessionId
            );
            
            return {
              content: [{
                type: "text",
                text: JSON.stringify({
                  action: "resume",
                  message: `Resumed session: ${resumeSession.id}`,
                  strategy: resumeSession.strategy,
                  thoughtCount: resumeSession.thoughtHistory.length,
                  currentState: semanticResponse.currentState,
                  absoluteThoughtNumber: thinkingServer.absoluteThoughtNumber,
                  sequenceThoughtNumber: thinkingServer.sequenceThoughtNumber,
                  availableActions: semanticResponse.availableActions,
                  lastThought: lastThought?.thought?.substring(0, 200) + (lastThought?.thought?.length > 200 ? '...' : '')
                }, null, 2)
              }]
            };
    
          default:
            throw new Error(`Unknown action: ${args.action}`);
        }
      } catch (error) {
        console.error(`ERROR: Session manager action '${args.action}' failed:`, error);
        throw error;
      }
    }
  • Zod schema for input validation of the think-session-manager tool, defining parameters for different actions like listing, searching, and filtering sessions.
      action: z.enum(["list", "get", "resume", "search"]),
      sessionId: z.string().optional(),
      limit: z.number().optional().default(10),
      offset: z.number().optional().default(0),
      query: z.string().optional(),
      strategy: z.string().optional(),
      includeTopics: z.boolean().optional().default(false),
      sortBy: z.enum(["timestamp", "thoughtCount", "complexity", "quality", "duration"]).optional().default("timestamp"),
      completionStatus: z.enum(["empty", "started", "in-progress", "completed"]).optional(),
      minScore: z.number().optional().default(0.1)
    },
  • index.js:1861-2087 (registration)
    MCP server tool registration for 'think-session-manager', including name, description, schema, and handler reference.
      "think-session-manager",
      "Manage thinking sessions: list with pagination/filtering, search by content, get detailed analysis, or resume previous sessions",
      {
        action: z.enum(["list", "get", "resume", "search"]),
        sessionId: z.string().optional(),
        limit: z.number().optional().default(10),
        offset: z.number().optional().default(0),
        query: z.string().optional(),
        strategy: z.string().optional(),
        includeTopics: z.boolean().optional().default(false),
        sortBy: z.enum(["timestamp", "thoughtCount", "complexity", "quality", "duration"]).optional().default("timestamp"),
        completionStatus: z.enum(["empty", "started", "in-progress", "completed"]).optional(),
        minScore: z.number().optional().default(0.1)
      },
      async (args) => {
        try {
          switch (args.action) {
            case "list":
              const listOptions = {
                limit: args.limit,
                offset: args.offset,
                strategy: args.strategy,
                includeTopics: args.includeTopics,
                sortBy: args.sortBy
              };
              
              const listResult = await sessionStorage.listSessions(listOptions);
              
              return {
                content: [{
                  type: "text",
                  text: JSON.stringify({
                    action: "list",
                    total: listResult.total,
                    offset: listResult.offset,
                    limit: listResult.limit,
                    hasMore: listResult.hasMore,
                    sessions: listResult.sessions.map(s => ({
                      sessionId: s.id.split('-').slice(-2).join('-'), // Extract timestamp portion  
                      description: s.sessionPurpose || s.topics?.[0]?.term + ' analysis' || 'General thinking session',
                      strategy: s.strategy,
                      quality: s.qualityRating ? Object.values(s.qualityRating).filter(v => typeof v === 'number').reduce((sum, val, _, arr) => sum + val / arr.length, 0).toFixed(1) : null,
                      duration: s.automaticMetrics?.duration || null,
                      thoughtCount: s.thoughtCount,
                      completion: s.completion,
                      complexity: s.complexity,
                      age: s.age
                    }))
                  }, null, 2)
                }]
              };
    
            case "get":
              if (!args.sessionId) {
                throw new Error("sessionId required for 'get' action");
              }
              
              // Handle both full session ID and shortened version
              let fullSessionId = args.sessionId;
              if (!args.sessionId.includes('session-')) {
                // If it's a shortened ID, we need to find the full ID
                const allSessions = await sessionStorage.listSessions({ limit: 1000 });
                const matchingSession = allSessions.sessions.find(s => 
                  s.id.split('-').slice(-2).join('-') === args.sessionId
                );
                if (matchingSession) {
                  fullSessionId = matchingSession.id;
                }
              }
              
              const session = await sessionStorage.getSession(fullSessionId);
              
              if (!session) {
                throw new Error(`Session not found: ${args.sessionId}`);
              }
              
              const sessionAnalysis = {
                id: session.id,
                strategy: session.strategy,
                timestamp: session.timestamp,
                thoughtHistory: session.thoughtHistory.map(t => ({
                  thoughtNumber: t.thoughtNumber,
                  absoluteNumber: t.absoluteNumber,
                  sequenceNumber: t.sequenceNumber,
                  thought: t.thought.substring(0, 150) + (t.thought.length > 150 ? '...' : ''),
                  stage: t.currentStage,
                  hasActions: t.plannedActions ? t.plannedActions.length > 0 : false,
                  hasResults: t.actionResults ? t.actionResults.length > 0 : false
                })),
                branches: Object.keys(session.branches),
                topics: sessionStorage.extractTopics(session.thoughtHistory),
                complexity: sessionStorage.calculateComplexity(session.thoughtHistory, session.branches),
                completion: sessionStorage.getCompletionStatus(session.thoughtHistory),
                summary: {
                  totalThoughts: session.thoughtHistory.length,
                  totalBranches: Object.keys(session.branches).length,
                  finalStage: session.thoughtHistory[session.thoughtHistory.length - 1]?.currentStage,
                  completed: !session.thoughtHistory[session.thoughtHistory.length - 1]?.nextThoughtNeeded
                }
              };
              
              return {
                content: [{
                  type: "text",
                  text: JSON.stringify({
                    action: "get",
                    ...sessionAnalysis
                  }, null, 2)
                }]
              };
    
            case "search":
              if (!args.query) {
                throw new Error("query required for 'search' action");
              }
              
              const searchOptions = {
                limit: args.limit,
                offset: args.offset,
                minScore: args.minScore,
                strategy: args.strategy,
                completionStatus: args.completionStatus
              };
              
              const searchResult = await sessionStorage.searchSessions(args.query, searchOptions);
              
              return {
                content: [{
                  type: "text",
                  text: JSON.stringify({
                    action: "search",
                    query: args.query,
                    ...searchResult,
                    sessions: searchResult.sessions.map(s => ({
                      sessionId: s.id.split('-').slice(-2).join('-'), // Extract timestamp portion for shorter ID
                      description: s.sessionPurpose || s.thoughtHistory[0]?.thought.substring(0, 100) + '...' || 'No description available',
                      strategy: s.strategy,
                      quality: s.qualityRating ? Object.values(s.qualityRating).filter(v => typeof v === 'number').reduce((sum, val, _, arr) => sum + val / arr.length, 0).toFixed(1) : null,
                      duration: s.automaticMetrics?.duration || null,
                      completion: s.completion,
                      searchScore: s.searchScore.toFixed(2),
                      matchedTerms: s.matchedTerms,
                      age: `${Math.round((Date.now() - new Date(s.timestamp)) / (1000 * 60))}m ago`
                    }))
                  }, null, 2)
                }]
              };
    
            case "resume":
              if (!args.sessionId) {
                throw new Error("sessionId required for 'resume' action");
              }
              
              // Handle both full session ID and shortened version
              let fullResumeSessionId = args.sessionId;
              if (!args.sessionId.includes('session-')) {
                // If it's a shortened ID, we need to find the full ID
                const allSessions = await sessionStorage.listSessions({ limit: 1000 });
                const matchingSession = allSessions.sessions.find(s => 
                  s.id.split('-').slice(-2).join('-') === args.sessionId
                );
                if (matchingSession) {
                  fullResumeSessionId = matchingSession.id;
                }
              }
              
              const resumeSession = await sessionStorage.getSession(fullResumeSessionId);
              
              if (!resumeSession) {
                throw new Error(`Session not found: ${args.sessionId}`);
              }
              
              // Restore session state
              thinkingServer.thoughtHistory = resumeSession.thoughtHistory;
              thinkingServer.branches = resumeSession.branches;
              thinkingServer.sessionId = resumeSession.id;
              thinkingServer.strategy = resumeSession.strategy;
              thinkingServer.stageManager = new StageManager(resumeSession.strategy);
              thinkingServer.isInitialized = true;
              
              // Reconstruct dual numbering from history
              const lastThought = resumeSession.thoughtHistory[resumeSession.thoughtHistory.length - 1];
              thinkingServer.absoluteThoughtNumber = lastThought?.absoluteNumber || 0;
              thinkingServer.sequenceThoughtNumber = lastThought?.sequenceNumber || 0;
              
              // Set stage to last known stage
              if (lastThought?.currentStage) {
                thinkingServer.stageManager.currentStage = lastThought.currentStage;
              }
              
              console.error(`📚 Resumed session: ${resumeSession.id} (${resumeSession.strategy})`);
              
              // Build semantic response for current state
              const semanticResponse = thinkingServer.parameterRouter.buildSemanticResponse(
                thinkingServer.strategy,
                thinkingServer.stageManager.getCurrentStage(),
                thinkingServer.thoughtHistory,
                thinkingServer.sessionId
              );
              
              return {
                content: [{
                  type: "text",
                  text: JSON.stringify({
                    action: "resume",
                    message: `Resumed session: ${resumeSession.id}`,
                    strategy: resumeSession.strategy,
                    thoughtCount: resumeSession.thoughtHistory.length,
                    currentState: semanticResponse.currentState,
                    absoluteThoughtNumber: thinkingServer.absoluteThoughtNumber,
                    sequenceThoughtNumber: thinkingServer.sequenceThoughtNumber,
                    availableActions: semanticResponse.availableActions,
                    lastThought: lastThought?.thought?.substring(0, 200) + (lastThought?.thought?.length > 200 ? '...' : '')
                  }, null, 2)
                }]
              };
    
            default:
              throw new Error(`Unknown action: ${args.action}`);
          }
        } catch (error) {
          console.error(`ERROR: Session manager action '${args.action}' failed:`, error);
          throw error;
        }
      }
    );
  • ThinkingSessionStorage class providing helper methods for session persistence, listing, searching, analysis (topics, complexity, completion status), and resumption. Used by the tool handler for all session operations.
    class ThinkingSessionStorage {
        constructor(storagePath) {
            this.storagePath = storagePath;
        }
    
        calculateAutomaticMetrics(thoughtHistory, sessionStartTime, hasActions) {
            const sessionEndTime = new Date().toISOString();
            const durationMs = new Date(sessionEndTime) - new Date(sessionStartTime);
            const durationMinutes = Math.round(durationMs / (1000 * 60));
            
            // Calculate iteration ratio (revisions / total thoughts)
            const revisionCount = thoughtHistory.filter(t => t.isRevision).length;
            const iterationRatio = thoughtHistory.length > 0 ? revisionCount / thoughtHistory.length : 0;
            
            return {
                duration: durationMinutes,
                iterationRatio: Math.round(iterationRatio * 100) / 100, // Round to 2 decimals
                toolIntegration: hasActions
            };
        }
    
        async saveSession(sessionId, thoughtHistory, branches, sessionPurpose = null, qualityRating = null, sessionStartTime = null) {
            const sessionDir = path.join(this.storagePath, sessionId);
            await fs.ensureDir(sessionDir);
            
            // Calculate automatic metrics
            const hasActions = thoughtHistory.some(t => t.plannedActions || t.actionResults);
            const automaticMetrics = sessionStartTime ? 
                this.calculateAutomaticMetrics(thoughtHistory, sessionStartTime, hasActions) : 
                null;
            
            const sessionData = {
                id: sessionId,
                timestamp: new Date().toISOString(),
                strategy: sessionId.split('-')[0], // Extract strategy from sessionId
                sessionPurpose,
                qualityRating,
                automaticMetrics,
                thoughtHistory,
                branches
            };
            
            const filePath = path.join(sessionDir, 'session.json');
            await fs.writeJson(filePath, sessionData, { spaces: 2 });
            
            return filePath;
        }
    
        // Extract key topics and terms from thought content
        extractTopics(thoughtHistory) {
            const allText = thoughtHistory.map(t => t.thought).join(' ');
            
            // Simple topic extraction - remove common words and get meaningful terms
            const stopWords = new Set(['the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'from', 'up', 'about', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'between', 'among', 'this', 'that', 'these', 'those', 'i', 'you', 'he', 'she', 'it', 'we', 'they', 'me', 'him', 'her', 'us', 'them', 'my', 'your', 'his', 'her', 'its', 'our', 'their', 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could', 'should', 'may', 'might', 'must', 'can', 'shall']);
            
            const words = allText.toLowerCase()
                .replace(/[^\w\s]/g, ' ')
                .split(/\s+/)
                .filter(word => word.length > 2 && !stopWords.has(word));
            
            // Count word frequency
            const wordCounts = {};
            words.forEach(word => {
                wordCounts[word] = (wordCounts[word] || 0) + 1;
            });
            
            // Get top terms
            const topTerms = Object.entries(wordCounts)
                .sort(([,a], [,b]) => b - a)
                .slice(0, 8)
                .map(([word, count]) => ({ term: word, frequency: count }));
            
            return topTerms;
        }
    
        // Calculate session complexity based on branching, actions, and thought depth
        calculateComplexity(thoughtHistory, branches) {
            const thoughtComplexity = thoughtHistory.length;
            const branchComplexity = Object.keys(branches).length * 2;
            const actionComplexity = thoughtHistory.filter(t => t.plannedActions || t.actionResults).length;
            
            const totalComplexity = thoughtComplexity + branchComplexity + actionComplexity;
            
            if (totalComplexity < 5) return 'simple';
            if (totalComplexity < 15) return 'moderate';
            if (totalComplexity < 30) return 'complex';
            return 'very-complex';
        }
    
        // Determine completion status
        getCompletionStatus(thoughtHistory) {
            if (thoughtHistory.length === 0) return 'empty';
            
            const lastThought = thoughtHistory[thoughtHistory.length - 1];
            const hasConclusion = lastThought.finalAnswer || !lastThought.nextThoughtNeeded;
            const hasSignificantProgress = thoughtHistory.length >= 3;
            
            if (hasConclusion && hasSignificantProgress) return 'completed';
            if (hasSignificantProgress) return 'in-progress';
            return 'started';
        }
    
        // Search sessions by content with term scoring
        async searchSessions(query, options = {}) {
            const {
                limit = 10,
                offset = 0,
                minScore = 0.1,
                strategy = null,
                completionStatus = null
            } = options;
            
            const sessions = await this.getAllSessions();
            const queryTerms = query.toLowerCase().split(/\s+/).filter(term => term.length > 2);
            
            const scoredSessions = sessions.map(session => {
                const sessionText = session.thoughtHistory.map(t => t.thought).join(' ').toLowerCase();
                
                // Calculate relevance score
                let score = 0;
                queryTerms.forEach(term => {
                    const regex = new RegExp(term, 'gi');
                    const matches = sessionText.match(regex) || [];
                    score += matches.length;
                });
                
                // Normalize by session length
                const normalizedScore = score / Math.max(sessionText.length / 1000, 1);
                
                return {
                    ...session,
                    searchScore: normalizedScore,
                    matchedTerms: queryTerms.filter(term => sessionText.includes(term))
                };
            });
            
            // Filter and sort by relevance
            let filteredSessions = scoredSessions
                .filter(s => s.searchScore >= minScore)
                .filter(s => !strategy || s.strategy === strategy)
                .filter(s => !completionStatus || s.completion === completionStatus)
                .sort((a, b) => b.searchScore - a.searchScore);
            
            const total = filteredSessions.length;
            filteredSessions = filteredSessions.slice(offset, offset + limit);
            
            return {
                sessions: filteredSessions,
                total,
                offset,
                limit,
                hasMore: offset + limit < total
            };
        }
    
        async listSessions(options = {}) {
            const {
                limit = 10,
                offset = 0,
                strategy = null,
                includeTopics = false,
                sortBy = 'timestamp' // timestamp, thoughtCount, complexity
            } = options;
            
            await fs.ensureDir(this.storagePath);
            const dirs = await fs.readdir(this.storagePath);
            
            const sessions = [];
            for (const dir of dirs) {
                const sessionPath = path.join(this.storagePath, dir, 'session.json');
                if (await fs.pathExists(sessionPath)) {
                    try {
                        const sessionData = await fs.readJson(sessionPath);
                        
                        const sessionInfo = {
                            id: sessionData.id,
                            timestamp: sessionData.timestamp,
                            strategy: sessionData.strategy || "unknown",
                            sessionPurpose: sessionData.sessionPurpose,
                            qualityRating: sessionData.qualityRating,
                            automaticMetrics: sessionData.automaticMetrics,
                            thoughtCount: sessionData.thoughtHistory.length,
                            branchCount: Object.keys(sessionData.branches).length,
                            completion: this.getCompletionStatus(sessionData.thoughtHistory),
                            complexity: this.calculateComplexity(sessionData.thoughtHistory, sessionData.branches),
                            age: `${Math.round((Date.now() - new Date(sessionData.timestamp)) / (1000 * 60))} minutes ago`
                        };
                        
                        if (includeTopics) {
                            sessionInfo.topics = this.extractTopics(sessionData.thoughtHistory);
                        }
                        
                        sessions.push(sessionInfo);
                    } catch (error) {
                        console.error(`Error reading session ${dir}:`, error);
                    }
                }
            }
            
            // Filter by strategy if specified
            const filteredSessions = strategy ? 
                sessions.filter(s => s.strategy === strategy) : 
                sessions;
            
            // Sort sessions
            filteredSessions.sort((a, b) => {
                switch (sortBy) {
                    case 'thoughtCount':
                        return b.thoughtCount - a.thoughtCount;
                    case 'complexity':
                        const complexityOrder = { 'simple': 1, 'moderate': 2, 'complex': 3, 'very-complex': 4 };
                        return complexityOrder[b.complexity] - complexityOrder[a.complexity];
                    case 'quality':
                        // Calculate average quality score
                        const getAvgQuality = (session) => {
                            if (!session.qualityRating) return 0;
                            const ratings = Object.values(session.qualityRating).filter(v => typeof v === 'number');
                            return ratings.length > 0 ? ratings.reduce((sum, val) => sum + val, 0) / ratings.length : 0;
                        };
                        return getAvgQuality(b) - getAvgQuality(a);
                    case 'duration':
                        const getDuration = (session) => session.automaticMetrics?.duration || 0;
                        return getDuration(a) - getDuration(b); // Shorter duration first (more efficient)
                    case 'timestamp':
                    default:
                        return new Date(b.timestamp) - new Date(a.timestamp);
                }
            });
            
            const total = filteredSessions.length;
            const paginatedSessions = filteredSessions.slice(offset, offset + limit);
            
            return {
                sessions: paginatedSessions,
                total,
                offset,
                limit,
                hasMore: offset + limit < total
            };
        }
    
        async getAllSessions() {
            await fs.ensureDir(this.storagePath);
            const dirs = await fs.readdir(this.storagePath);
            
            const sessions = [];
            for (const dir of dirs) {
                const sessionPath = path.join(this.storagePath, dir, 'session.json');
                if (await fs.pathExists(sessionPath)) {
                    try {
                        const sessionData = await fs.readJson(sessionPath);
                        sessions.push({
                            ...sessionData,
                            completion: this.getCompletionStatus(sessionData.thoughtHistory),
                            complexity: this.calculateComplexity(sessionData.thoughtHistory, sessionData.branches)
                        });
                    } catch (error) {
                        console.error(`Error reading session ${dir}:`, error);
                    }
                }
            }
            
            return sessions;
        }
    
        async getSession(sessionId) {
            const sessionPath = path.join(this.storagePath, sessionId, 'session.json');
            if (await fs.pathExists(sessionPath)) {
                return await fs.readJson(sessionPath);
            }
            return null;
        }
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions pagination/filtering for listing and content-based searching, but lacks details on permissions, rate limits, mutation effects (e.g., whether 'resume' modifies sessions), or response formats. For a tool with 10 parameters and multiple actions, this leaves significant behavioral gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core purpose and lists key actions. It avoids redundancy and wastes no words, though it could be slightly more structured (e.g., bullet points) given the complexity of actions and parameters.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (10 parameters, 4 actions, no annotations, no output schema), the description is incomplete. It lacks details on behavioral traits, parameter meanings, output expectations, and differentiation from siblings. The agent would struggle to use this tool effectively without additional context or trial-and-error.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate for undocumented parameters. It only loosely maps to parameters: 'list with pagination/filtering' hints at 'limit', 'offset', and filtering parameters; 'search by content' hints at 'query'; 'get detailed analysis' and 'resume' hint at 'sessionId'. However, it omits many parameters (e.g., 'strategy', 'includeTopics', 'sortBy', 'completionStatus', 'minScore') and doesn't explain their semantics, failing to adequately compensate for the coverage gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose as managing thinking sessions with four specific actions: list, search, get detailed analysis, and resume. It uses specific verbs and identifies the resource as 'thinking sessions,' but doesn't explicitly differentiate from sibling tools like 'think-strategies' or 'think-tools' beyond the general domain.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus the sibling tools 'think-strategies' or 'think-tools.' It lists the available actions but doesn't explain when to choose one action over another or any prerequisites for usage. The agent must infer usage from the action parameter alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aaronsb/think-strategies'

If you have feedback or need assistance with the MCP directory API, please join our Discord server