main
  1package thinking
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6	"os"
  7	"strconv"
  8	"strings"
  9	"sync"
 10	"time"
 11
 12	"github.com/xlgmokha/mcp/pkg/mcp"
 13)
 14
 15// ThinkingOperations provides thinking session management operations
 16type ThinkingOperations struct {
 17	sessions     map[string]*ThinkingSession
 18	branches     map[string]*Branch
 19	mu           sync.RWMutex
 20	persistFile  string
 21}
 22
 23// ThinkingSession represents a thinking session with sequential thoughts
 24type ThinkingSession struct {
 25	ID             string    `json:"id"`
 26	Thoughts       []Thought `json:"thoughts"`
 27	CurrentThought int       `json:"current_thought"`
 28	TotalThoughts  int       `json:"total_thoughts"`
 29	Status         string    `json:"status"` // "active", "completed"
 30	CreatedAt      time.Time `json:"created_at"`
 31	LastActivity   time.Time `json:"last_activity"`
 32	ActiveBranches []string  `json:"active_branches"`
 33}
 34
 35// Branch represents a reasoning branch with its own thought sequence
 36type Branch struct {
 37	ID             string    `json:"id"`
 38	SessionID      string    `json:"session_id"`
 39	FromThought    int       `json:"from_thought"`
 40	Thoughts       []Thought `json:"thoughts"`
 41	CreatedAt      time.Time `json:"created_at"`
 42	LastActivity   time.Time `json:"last_activity"`
 43}
 44
 45// Thought represents a single thought in the sequence
 46type Thought struct {
 47	Number            int    `json:"number"`
 48	Content           string `json:"content"`
 49	IsRevision        bool   `json:"is_revision,omitempty"`
 50	RevisesThought    *int   `json:"revises_thought,omitempty"`
 51	BranchFromThought *int   `json:"branch_from_thought,omitempty"`
 52	BranchID          string `json:"branch_id,omitempty"`
 53	NeedsMoreThoughts bool   `json:"needs_more_thoughts,omitempty"`
 54}
 55
 56// ThinkingResponse represents the response structure
 57type ThinkingResponse struct {
 58	Thought            string   `json:"thought"`
 59	ThoughtNumber      int      `json:"thought_number"`
 60	TotalThoughts      int      `json:"total_thoughts"`
 61	NextThoughtNeeded  bool     `json:"next_thought_needed"`
 62	Status             string   `json:"status"`
 63	Solution           string   `json:"solution,omitempty"`
 64	SessionID          string   `json:"session_id"`
 65	ThoughtHistorySize int      `json:"thought_history_size"`
 66	ActiveBranches     []string `json:"active_branches"`
 67	BranchContext      string   `json:"branch_context,omitempty"`
 68}
 69
 70// NewThinkingOperations creates a new ThinkingOperations helper
 71func NewThinkingOperations(persistFile string) *ThinkingOperations {
 72	thinking := &ThinkingOperations{
 73		sessions:    make(map[string]*ThinkingSession),
 74		branches:    make(map[string]*Branch),
 75		persistFile: persistFile,
 76	}
 77
 78	// Load existing sessions if persistence file is provided
 79	if persistFile != "" {
 80		thinking.loadSessions()
 81	}
 82
 83	return thinking
 84}
 85
 86// New creates a new Sequential Thinking MCP server
 87func New() *mcp.Server {
 88	return NewWithPersistence("")
 89}
 90
 91// NewWithPersistence creates a new Sequential Thinking MCP server with optional persistence
 92func NewWithPersistence(persistFile string) *mcp.Server {
 93	thinking := NewThinkingOperations(persistFile)
 94	builder := mcp.NewServerBuilder("mcp-sequential-thinking", "1.0.0")
 95
 96	// Add sequentialthinking tool
 97	builder.AddTool(mcp.NewTool("sequentialthinking", "A detailed tool for dynamic and reflective problem-solving through thoughts. This tool helps analyze problems through a flexible thinking process that can adapt and evolve. Each thought can build on, question, or revise previous insights as understanding deepens.", map[string]interface{}{
 98		"type": "object",
 99		"properties": map[string]interface{}{
100			"thought": map[string]interface{}{
101				"type":        "string",
102				"description": "Your current thinking step",
103			},
104			"nextThoughtNeeded": map[string]interface{}{
105				"type":        "boolean",
106				"description": "Whether another thought step is needed",
107			},
108			"thoughtNumber": map[string]interface{}{
109				"type":        "integer",
110				"minimum":     1,
111				"description": "Current thought number",
112			},
113			"totalThoughts": map[string]interface{}{
114				"type":        "integer",
115				"minimum":     1,
116				"description": "Estimated total thoughts needed",
117			},
118			"isRevision": map[string]interface{}{
119				"type":        "boolean",
120				"description": "Whether this revises previous thinking",
121				"default":     false,
122			},
123			"revisesThought": map[string]interface{}{
124				"type":        "integer",
125				"minimum":     1,
126				"description": "Which thought is being reconsidered",
127			},
128			"branchFromThought": map[string]interface{}{
129				"type":        "integer",
130				"minimum":     1,
131				"description": "Branching point thought number",
132			},
133			"branchId": map[string]interface{}{
134				"type":        "string",
135				"description": "Branch identifier",
136			},
137			"needsMoreThoughts": map[string]interface{}{
138				"type":        "boolean",
139				"description": "If more thoughts are needed",
140				"default":     false,
141			},
142			"sessionId": map[string]interface{}{
143				"type":        "string",
144				"description": "Session ID for thought continuity (optional, auto-generated if not provided)",
145			},
146		},
147		"required": []string{"thought", "nextThoughtNeeded", "thoughtNumber", "totalThoughts"},
148	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
149		return thinking.handleSequentialThinking(req)
150	}))
151
152	// Add get_session_history tool
153	builder.AddTool(mcp.NewTool("get_session_history", "Get the complete thought history for a thinking session", map[string]interface{}{
154		"type": "object",
155		"properties": map[string]interface{}{
156			"sessionId": map[string]interface{}{
157				"type":        "string",
158				"description": "Session ID to get history for",
159			},
160		},
161		"required": []string{"sessionId"},
162	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
163		return thinking.handleGetSessionHistory(req)
164	}))
165
166	// Add list_sessions tool
167	builder.AddTool(mcp.NewTool("list_sessions", "List all active thinking sessions", map[string]interface{}{
168		"type":       "object",
169		"properties": map[string]interface{}{},
170	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
171		return thinking.handleListSessions(req)
172	}))
173
174	// Add get_branch_history tool
175	builder.AddTool(mcp.NewTool("get_branch_history", "Get the thought history for a specific reasoning branch", map[string]interface{}{
176		"type": "object",
177		"properties": map[string]interface{}{
178			"branchId": map[string]interface{}{
179				"type":        "string",
180				"description": "Branch ID to get history for",
181			},
182		},
183		"required": []string{"branchId"},
184	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
185		return thinking.handleGetBranchHistory(req)
186	}))
187
188	// Add clear_session tool
189	builder.AddTool(mcp.NewTool("clear_session", "Clear a thinking session and all its branches", map[string]interface{}{
190		"type": "object",
191		"properties": map[string]interface{}{
192			"sessionId": map[string]interface{}{
193				"type":        "string",
194				"description": "Session ID to clear",
195			},
196		},
197		"required": []string{"sessionId"},
198	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
199		return thinking.handleClearSession(req)
200	}))
201
202	return builder.Build()
203}
204
205// Helper methods for ThinkingOperations
206
207func (thinking *ThinkingOperations) handleSequentialThinking(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
208	// Parse and validate input parameters
209	params, err := thinking.parseThinkingParameters(req.Arguments)
210	if err != nil {
211		return mcp.NewToolError(err.Error()), nil
212	}
213
214	thinking.mu.Lock()
215	defer thinking.mu.Unlock()
216
217	// Get or create session
218	session := thinking.getOrCreateSession(params.SessionID)
219	
220	// Create thought object
221	currentThought := Thought{
222		Number:            params.ThoughtNumber,
223		Content:           params.Thought,
224		IsRevision:        params.IsRevision,
225		RevisesThought:    params.RevisesThought,
226		BranchFromThought: params.BranchFromThought,
227		BranchID:          params.BranchID,
228		NeedsMoreThoughts: params.NeedsMoreThoughts,
229	}
230
231	// Handle branching
232	var activeBranch *Branch
233	if params.BranchFromThought != nil && params.BranchID != "" {
234		activeBranch = thinking.getOrCreateBranch(session.ID, params.BranchID, *params.BranchFromThought)
235		activeBranch.Thoughts = append(activeBranch.Thoughts, currentThought)
236		activeBranch.LastActivity = time.Now()
237	} else {
238		// Add to main session
239		session.Thoughts = append(session.Thoughts, currentThought)
240	}
241
242	// Update session state
243	session.CurrentThought = params.ThoughtNumber
244	session.TotalThoughts = params.TotalThoughts
245	session.LastActivity = time.Now()
246
247	// Save to persistence if configured
248	thinking.saveSessions()
249
250	// Determine status
251	status := "thinking"
252	if !params.NextThoughtNeeded {
253		status = "completed"
254	} else if params.ThoughtNumber >= params.TotalThoughts && !params.NeedsMoreThoughts {
255		status = "completed"
256	}
257	session.Status = status
258
259	// Create response with session context
260	response := ThinkingResponse{
261		Thought:            params.Thought,
262		ThoughtNumber:      params.ThoughtNumber,
263		TotalThoughts:      params.TotalThoughts,
264		NextThoughtNeeded:  params.NextThoughtNeeded,
265		Status:             status,
266		SessionID:          session.ID,
267		ThoughtHistorySize: len(session.Thoughts),
268		ActiveBranches:     session.ActiveBranches,
269	}
270
271	// Add branch context if applicable
272	if activeBranch != nil {
273		response.BranchContext = fmt.Sprintf("Branch %s (from thought %d)", params.BranchID, *params.BranchFromThought)
274	}
275
276	// If this is the final thought, try to extract a solution
277	if status == "completed" {
278		response.Solution = thinking.extractSolution(params.Thought)
279	}
280
281	// Format the result with session context
282	resultText := thinking.formatThinkingResultWithSession(response, currentThought, session, activeBranch)
283
284	return mcp.NewToolResult(mcp.NewTextContent(resultText)), nil
285}
286
287// Helper methods
288
289// PersistentData represents the data structure for persistence
290type PersistentData struct {
291	Sessions map[string]*ThinkingSession `json:"sessions"`
292	Branches map[string]*Branch          `json:"branches"`
293}
294
295// loadSessions loads sessions from persistence file
296func (thinking *ThinkingOperations) loadSessions() error {
297	if thinking.persistFile == "" {
298		return nil
299	}
300
301	if _, err := os.Stat(thinking.persistFile); os.IsNotExist(err) {
302		return nil // File doesn't exist, start fresh
303	}
304
305	data, err := os.ReadFile(thinking.persistFile)
306	if err != nil {
307		return err
308	}
309
310	if len(data) == 0 {
311		return nil // Empty file
312	}
313
314	var persistentData PersistentData
315	if err := json.Unmarshal(data, &persistentData); err != nil {
316		return err
317	}
318
319	thinking.sessions = persistentData.Sessions
320	thinking.branches = persistentData.Branches
321
322	// Initialize maps if nil
323	if thinking.sessions == nil {
324		thinking.sessions = make(map[string]*ThinkingSession)
325	}
326	if thinking.branches == nil {
327		thinking.branches = make(map[string]*Branch)
328	}
329
330	return nil
331}
332
333// saveSessions saves sessions to persistence file
334func (thinking *ThinkingOperations) saveSessions() error {
335	if thinking.persistFile == "" {
336		return nil
337	}
338
339	persistentData := PersistentData{
340		Sessions: thinking.sessions,
341		Branches: thinking.branches,
342	}
343
344	data, err := json.MarshalIndent(persistentData, "", "  ")
345	if err != nil {
346		return err
347	}
348
349	return os.WriteFile(thinking.persistFile, data, 0644)
350}
351
352// ThinkingParameters holds parsed parameters for thinking operations
353type ThinkingParameters struct {
354	Thought           string
355	NextThoughtNeeded bool
356	ThoughtNumber     int
357	TotalThoughts     int
358	IsRevision        bool
359	RevisesThought    *int
360	BranchFromThought *int
361	BranchID          string
362	NeedsMoreThoughts bool
363	SessionID         string
364}
365
366// parseThinkingParameters parses and validates input parameters
367func (thinking *ThinkingOperations) parseThinkingParameters(args map[string]interface{}) (*ThinkingParameters, error) {
368	params := &ThinkingParameters{}
369
370	// Required parameters
371	thought, ok := args["thought"].(string)
372	if !ok {
373		return nil, fmt.Errorf("thought parameter is required and must be a string")
374	}
375	params.Thought = thought
376
377	nextThoughtNeeded, ok := args["nextThoughtNeeded"].(bool)
378	if !ok {
379		return nil, fmt.Errorf("nextThoughtNeeded parameter is required and must be a boolean")
380	}
381	params.NextThoughtNeeded = nextThoughtNeeded
382
383	if tn, ok := args["thoughtNumber"]; ok {
384		switch v := tn.(type) {
385		case float64:
386			params.ThoughtNumber = int(v)
387		case int:
388			params.ThoughtNumber = v
389		default:
390			return nil, fmt.Errorf("thoughtNumber must be a number")
391		}
392		if params.ThoughtNumber < 1 {
393			return nil, fmt.Errorf("thoughtNumber must be >= 1")
394		}
395	} else {
396		params.ThoughtNumber = 1
397	}
398
399	if tt, ok := args["totalThoughts"]; ok {
400		switch v := tt.(type) {
401		case float64:
402			params.TotalThoughts = int(v)
403		case int:
404			params.TotalThoughts = v
405		default:
406			return nil, fmt.Errorf("totalThoughts must be a number")
407		}
408		if params.TotalThoughts < 1 {
409			return nil, fmt.Errorf("totalThoughts must be >= 1")
410		}
411	} else {
412		params.TotalThoughts = 1
413	}
414
415	// Optional parameters
416	if ir, ok := args["isRevision"].(bool); ok {
417		params.IsRevision = ir
418	}
419
420	if rt, ok := args["revisesThought"]; ok && params.IsRevision {
421		switch v := rt.(type) {
422		case float64:
423			val := int(v)
424			params.RevisesThought = &val
425		case int:
426			params.RevisesThought = &v
427		default:
428			return nil, fmt.Errorf("revisesThought must be a number")
429		}
430	}
431
432	if bft, ok := args["branchFromThought"]; ok {
433		switch v := bft.(type) {
434		case float64:
435			val := int(v)
436			params.BranchFromThought = &val
437		case int:
438			params.BranchFromThought = &v
439		default:
440			return nil, fmt.Errorf("branchFromThought must be a number")
441		}
442	}
443
444	if bid, ok := args["branchId"].(string); ok {
445		params.BranchID = bid
446	}
447
448	if nmt, ok := args["needsMoreThoughts"].(bool); ok {
449		params.NeedsMoreThoughts = nmt
450	}
451
452	if sid, ok := args["sessionId"].(string); ok {
453		params.SessionID = sid
454	}
455
456	return params, nil
457}
458
459// getOrCreateSession gets existing session or creates new one
460func (thinking *ThinkingOperations) getOrCreateSession(sessionID string) *ThinkingSession {
461	if sessionID == "" {
462		sessionID = thinking.generateSessionID()
463	}
464
465	if session, exists := thinking.sessions[sessionID]; exists {
466		return session
467	}
468
469	session := &ThinkingSession{
470		ID:             sessionID,
471		Thoughts:       make([]Thought, 0),
472		CurrentThought: 0,
473		TotalThoughts:  1,
474		Status:         "active",
475		CreatedAt:      time.Now(),
476		LastActivity:   time.Now(),
477		ActiveBranches: make([]string, 0),
478	}
479
480	thinking.sessions[sessionID] = session
481	return session
482}
483
484// getOrCreateBranch gets existing branch or creates new one
485func (thinking *ThinkingOperations) getOrCreateBranch(sessionID, branchID string, fromThought int) *Branch {
486	if branch, exists := thinking.branches[branchID]; exists {
487		return branch
488	}
489
490	branch := &Branch{
491		ID:           branchID,
492		SessionID:    sessionID,
493		FromThought:  fromThought,
494		Thoughts:     make([]Thought, 0),
495		CreatedAt:    time.Now(),
496		LastActivity: time.Now(),
497	}
498
499	thinking.branches[branchID] = branch
500
501	// Add to session's active branches
502	if session, exists := thinking.sessions[sessionID]; exists {
503		session.ActiveBranches = append(session.ActiveBranches, branchID)
504	}
505
506	return branch
507}
508
509// generateSessionID generates a unique session ID
510func (thinking *ThinkingOperations) generateSessionID() string {
511	return fmt.Sprintf("session_%d", time.Now().UnixNano())
512}
513
514// formatThinkingResultWithSession formats result with session context
515func (thinking *ThinkingOperations) formatThinkingResultWithSession(response ThinkingResponse, thought Thought, session *ThinkingSession, branch *Branch) string {
516	var result strings.Builder
517
518	// Header with session info
519	result.WriteString(fmt.Sprintf("🧠 Sequential Thinking - Thought %d/%d (Session: %s)\n",
520		response.ThoughtNumber, response.TotalThoughts, response.SessionID))
521	result.WriteString("═══════════════════════════════════════\n\n")
522
523	// Session context
524	result.WriteString(fmt.Sprintf("šŸ“‹ Session Info:\n"))
525	result.WriteString(fmt.Sprintf("   • Session ID: %s\n", session.ID))
526	result.WriteString(fmt.Sprintf("   • Total thoughts in session: %d\n", len(session.Thoughts)))
527	result.WriteString(fmt.Sprintf("   • Active branches: %d\n", len(session.ActiveBranches)))
528	if response.BranchContext != "" {
529		result.WriteString(fmt.Sprintf("   • Branch context: %s\n", response.BranchContext))
530	}
531	result.WriteString("\n")
532
533	// Context information
534	var contextInfo []string
535	if thought.IsRevision && thought.RevisesThought != nil {
536		contextInfo = append(contextInfo, fmt.Sprintf("Revising thought %d", *thought.RevisesThought))
537	}
538	if thought.BranchFromThought != nil {
539		contextInfo = append(contextInfo, fmt.Sprintf("Branching from thought %d", *thought.BranchFromThought))
540		if thought.BranchID != "" {
541			contextInfo = append(contextInfo, fmt.Sprintf("Branch: %s", thought.BranchID))
542		}
543	}
544	if thought.NeedsMoreThoughts {
545		contextInfo = append(contextInfo, "Requesting additional thoughts beyond initial estimate")
546	}
547
548	if len(contextInfo) > 0 {
549		result.WriteString("šŸ“ Context: " + strings.Join(contextInfo, ", ") + "\n\n")
550	}
551
552	// Main thought content
553	result.WriteString("šŸ’­ Current Thought:\n")
554	result.WriteString(response.Thought + "\n\n")
555
556	// Progress indicator
557	progressBar := thinking.createProgressBar(response.ThoughtNumber, response.TotalThoughts)
558	result.WriteString("šŸ“Š Progress: " + progressBar + "\n\n")
559
560	// Status
561	statusEmoji := "šŸ”„"
562	if response.Status == "completed" {
563		statusEmoji = "āœ…"
564	}
565	result.WriteString(fmt.Sprintf("%s Status: %s\n", statusEmoji, response.Status))
566
567	if response.NextThoughtNeeded {
568		result.WriteString("ā­ļø  Next thought needed\n")
569	} else {
570		result.WriteString("šŸ Thinking sequence complete\n")
571	}
572
573	// Solution if available
574	if response.Solution != "" {
575		result.WriteString("\nšŸŽÆ Extracted Solution:\n")
576		result.WriteString(response.Solution + "\n")
577	}
578
579	// JSON data for programmatic access
580	result.WriteString("\nšŸ“‹ Structured Data:\n")
581	jsonData, _ := json.MarshalIndent(response, "", "  ")
582	result.WriteString("```json\n")
583	result.WriteString(string(jsonData))
584	result.WriteString("\n```")
585
586	return result.String()
587}
588
589func (thinking *ThinkingOperations) extractSolution(finalThought string) string {
590	// Simple heuristic to extract a solution from the final thought
591	content := strings.ToLower(finalThought)
592
593	// Look for solution indicators (with and without colons)
594	solutionKeywords := []string{
595		"solution:",
596		"solution is",
597		"answer:",
598		"answer is",
599		"conclusion:",
600		"final answer:",
601		"result:",
602		"therefore:",
603		"therefore,",
604		"therefore the",
605		"in conclusion:",
606	}
607
608	for _, keyword := range solutionKeywords {
609		if idx := strings.Index(content, keyword); idx != -1 {
610			// Extract text after the keyword
611			remaining := strings.TrimSpace(finalThought[idx+len(keyword):])
612			if len(remaining) > 0 {
613				// Take up to the first sentence or 200 characters
614				if sentences := strings.Split(remaining, "."); len(sentences) > 0 {
615					solution := strings.TrimSpace(sentences[0])
616					if len(solution) > 200 {
617						solution = solution[:200] + "..."
618					}
619					if solution != "" {
620						return solution
621					}
622				}
623			}
624		}
625	}
626
627	// If no explicit solution found, return the last sentence or a portion
628	sentences := strings.Split(strings.TrimSpace(finalThought), ".")
629	if len(sentences) > 0 {
630		lastSentence := strings.TrimSpace(sentences[len(sentences)-1])
631		if len(lastSentence) > 200 {
632			lastSentence = lastSentence[:200] + "..."
633		}
634		if lastSentence != "" {
635			return lastSentence
636		}
637	}
638
639	return "Solution extracted from final thought"
640}
641
642func (thinking *ThinkingOperations) formatThinkingResult(response ThinkingResponse, thought Thought, contextInfo []string) string {
643	var result strings.Builder
644
645	// Header
646	result.WriteString(fmt.Sprintf("🧠 Sequential Thinking - Thought %d/%d\n",
647		response.ThoughtNumber, response.TotalThoughts))
648	result.WriteString("═══════════════════════════════════════\n\n")
649
650	// Context information if any
651	if len(contextInfo) > 0 {
652		result.WriteString("šŸ“ Context: " + strings.Join(contextInfo, ", ") + "\n\n")
653	}
654
655	// Main thought content
656	result.WriteString("šŸ’­ Current Thought:\n")
657	result.WriteString(response.Thought + "\n\n")
658
659	// Progress indicator
660	progressBar := thinking.createProgressBar(response.ThoughtNumber, response.TotalThoughts)
661	result.WriteString("šŸ“Š Progress: " + progressBar + "\n\n")
662
663	// Status
664	statusEmoji := "šŸ”„"
665	if response.Status == "completed" {
666		statusEmoji = "āœ…"
667	}
668	result.WriteString(fmt.Sprintf("%s Status: %s\n", statusEmoji, response.Status))
669
670	if response.NextThoughtNeeded {
671		result.WriteString("ā­ļø  Next thought needed\n")
672	} else {
673		result.WriteString("šŸ Thinking sequence complete\n")
674	}
675
676	// Solution if available
677	if response.Solution != "" {
678		result.WriteString("\nšŸŽÆ Extracted Solution:\n")
679		result.WriteString(response.Solution + "\n")
680	}
681
682	// JSON data for programmatic access
683	result.WriteString("\nšŸ“‹ Structured Data:\n")
684	jsonData, _ := json.MarshalIndent(response, "", "  ")
685	result.WriteString("```json\n")
686	result.WriteString(string(jsonData))
687	result.WriteString("\n```")
688
689	return result.String()
690}
691
692func (thinking *ThinkingOperations) createProgressBar(current, total int) string {
693	if total <= 0 {
694		return "[ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ] 100%"
695	}
696
697	percentage := float64(current) / float64(total) * 100
698	if percentage > 100 {
699		percentage = 100
700	}
701
702	barLength := 20
703	filledLength := int(percentage / 100 * float64(barLength))
704
705	bar := "["
706	for i := 0; i < barLength; i++ {
707		if i < filledLength {
708			bar += "ā–ˆ"
709		} else {
710			bar += "ā–‘"
711		}
712	}
713	bar += "] " + strconv.Itoa(int(percentage)) + "%"
714
715	return bar
716}
717
718// New tool handlers for session management
719
720func (thinking *ThinkingOperations) handleGetSessionHistory(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
721	sessionID, ok := req.Arguments["sessionId"].(string)
722	if !ok {
723		return mcp.NewToolError("sessionId parameter is required"), nil
724	}
725
726	thinking.mu.RLock()
727	defer thinking.mu.RUnlock()
728
729	session, exists := thinking.sessions[sessionID]
730	if !exists {
731		return mcp.NewToolError(fmt.Sprintf("Session %s not found", sessionID)), nil
732	}
733
734	result := map[string]interface{}{
735		"sessionId":      session.ID,
736		"status":         session.Status,
737		"totalThoughts":  len(session.Thoughts),
738		"createdAt":      session.CreatedAt,
739		"lastActivity":   session.LastActivity,
740		"activeBranches": session.ActiveBranches,
741		"thoughts":       session.Thoughts,
742	}
743
744	jsonData, _ := json.MarshalIndent(result, "", "  ")
745	return mcp.NewToolResult(mcp.NewTextContent(string(jsonData))), nil
746}
747
748func (thinking *ThinkingOperations) handleListSessions(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
749	thinking.mu.RLock()
750	defer thinking.mu.RUnlock()
751
752	sessions := make([]map[string]interface{}, 0, len(thinking.sessions))
753	for _, session := range thinking.sessions {
754		sessionInfo := map[string]interface{}{
755			"sessionId":      session.ID,
756			"status":         session.Status,
757			"thoughtCount":   len(session.Thoughts),
758			"branchCount":    len(session.ActiveBranches),
759			"createdAt":      session.CreatedAt,
760			"lastActivity":   session.LastActivity,
761		}
762		sessions = append(sessions, sessionInfo)
763	}
764
765	result := map[string]interface{}{
766		"sessions": sessions,
767		"total":    len(sessions),
768	}
769
770	jsonData, _ := json.MarshalIndent(result, "", "  ")
771	return mcp.NewToolResult(mcp.NewTextContent(string(jsonData))), nil
772}
773
774func (thinking *ThinkingOperations) handleGetBranchHistory(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
775	branchID, ok := req.Arguments["branchId"].(string)
776	if !ok {
777		return mcp.NewToolError("branchId parameter is required"), nil
778	}
779
780	thinking.mu.RLock()
781	defer thinking.mu.RUnlock()
782
783	branch, exists := thinking.branches[branchID]
784	if !exists {
785		return mcp.NewToolError(fmt.Sprintf("Branch %s not found", branchID)), nil
786	}
787
788	result := map[string]interface{}{
789		"branchId":     branch.ID,
790		"sessionId":    branch.SessionID,
791		"fromThought":  branch.FromThought,
792		"thoughtCount": len(branch.Thoughts),
793		"createdAt":    branch.CreatedAt,
794		"lastActivity": branch.LastActivity,
795		"thoughts":     branch.Thoughts,
796	}
797
798	jsonData, _ := json.MarshalIndent(result, "", "  ")
799	return mcp.NewToolResult(mcp.NewTextContent(string(jsonData))), nil
800}
801
802func (thinking *ThinkingOperations) handleClearSession(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
803	sessionID, ok := req.Arguments["sessionId"].(string)
804	if !ok {
805		return mcp.NewToolError("sessionId parameter is required"), nil
806	}
807
808	thinking.mu.Lock()
809	defer thinking.mu.Unlock()
810
811	session, exists := thinking.sessions[sessionID]
812	if !exists {
813		return mcp.NewToolError(fmt.Sprintf("Session %s not found", sessionID)), nil
814	}
815
816	// Remove associated branches
817	for _, branchID := range session.ActiveBranches {
818		delete(thinking.branches, branchID)
819	}
820
821	// Remove the session
822	delete(thinking.sessions, sessionID)
823
824	// Save to persistence if configured
825	thinking.saveSessions()
826
827	result := map[string]interface{}{
828		"message":       fmt.Sprintf("Session %s and %d branches cleared", sessionID, len(session.ActiveBranches)),
829		"sessionId":     sessionID,
830		"branchesCount": len(session.ActiveBranches),
831	}
832
833	jsonData, _ := json.MarshalIndent(result, "", "  ")
834	return mcp.NewToolResult(mcp.NewTextContent(string(jsonData))), nil
835}