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}