Skip to content

mcp: add sequential thinking server example #51

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

nsega
Copy link

@nsega nsega commented Jun 26, 2025

Motivation and Context

This is part of #33. This PR adds example of implementing mcp on sequentialthinking server, from Example Servers.

The PR includes dynamic and reflective problem-solving through structured thinking processes.
The server provides tools for:

  • starting thinking sessions
  • adding sequential thoughts with step tracking
  • revising previous thoughts
  • creating alternative reasoning branches
  • reviewing complete processes.

Also, features include thread-safe session management, adaptive planning that adjusts step counts dynamically.

How Has This Been Tested?

Testing with 8 test functions covering all functionality with test code.

Breaking Changes

N/A

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Copy link
Author

@nsega nsega Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: Though we might not need to prepare for README.md for each example, I created this file, just in case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's definitely helpful.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. I appreciate your input.

@nsega nsega changed the title examples: add sequential thinking server example mcp: add sequential thinking server example Jun 26, 2025
- This example shows dynamic and reflective problem-solving through structured thinking processes.
- The server provides tools for starting thinking sessions, adding sequential thoughts with
step tracking, revising previous thoughts, creating alternative reasoning branches, and reviewing complete processes.
- Features include thread-safe session management, adaptive planning
that adjusts step counts dynamically.
- Add comprehensive testing with 8 test functions covering all functionality.

For modelcontextprotocol#33
@nsega nsega force-pushed the add-sequentialthinking-example branch from a71abdb to 9efbd13 Compare June 26, 2025 06:40
@jba
Copy link
Contributor

jba commented Jun 26, 2025

After this lands, it would be great to also add it to the servers repo. See #52.

Copy link
Contributor

@jba jba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a first round, focusing on local issues like naming.
I still don't really understand what this is doing; once I read the README more carefully I'll do another pass, probably tomorrow.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's definitely helpful.

- **Branch relationships**: Links to alternative reasoning paths
- **Status management**: Active, completed, or paused sessions

## Testing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove this section. It is already understood.


var httpAddr = flag.String("http", "", "if set, use streamable HTTP at this address, instead of stdin/stdout")

// Thought represents a single step in the thinking process
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: period at end of sentence

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is my careless. Just updated it.

}
}

func (s *SessionStore) GetSession(id string) (*ThinkingSession, bool) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the Get (idiomatic Go)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the Get by following idiomatic Go.


var httpAddr = flag.String("http", "", "if set, use streamable HTTP at this address, instead of stdin/stdout")

// Thought represents a single step in the thinking process
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"A Thought is..."

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the comment by following the format you suggested.

session.Status = "completed"
}

store.SetSession(session)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed It.

}

var review strings.Builder
review.WriteString(fmt.Sprintf("=== Thinking Review: %s ===\n", session.ID))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fmt.Fprintf(&review, "...",...)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. Updated these parts to using fmt.Fprint in ReviewThinking function.

}

// Resource handler for thinking sessions
func GetThinkingHistory(ctx context.Context, ss *mcp.ServerSession, params *mcp.ReadResourceParams) (*mcp.ReadResourceResult, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove Get

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed it.

// Resource handler for thinking sessions
func GetThinkingHistory(ctx context.Context, ss *mcp.ServerSession, params *mcp.ReadResourceParams) (*mcp.ReadResourceResult, error) {
// Extract session ID from URI (e.g., "thinking://session_123")
parts := strings.Split(params.URI, "://")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use url.Parse

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated it to use url.Parse.


// Add thinking tools
server.AddTools(
mcp.NewServerTool(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, this will soon be a bug.
According to the spec, tools with an output schema must provide a StructuredOutput field in the result.
We don't check for that today, but I'm working on the PR.
NewServerTool creates both an input and output schema.

Can you try creating these tools without NewServerTool, using the part of NewServerTool that does input schemas, and leaving out the outputschema? It would be useful to see how complicated that gets.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your explanation. I understand the current status of the PR you're working on.

Yes, I can. I tried to implement these tools without NewServerTool. As I may have missed something, I would appreciate it if you could provide me with further feedback on the implementation.

@MegaGrindStone MegaGrindStone mentioned this pull request Jun 26, 2025
9 tasks
- Remove "Get" prefix from getter methods (idiomatic Go)
- Rename Thought.ID to Thought.Index (more accurate)
- Change EstimatedTotal and CreateBranch from pointers to plain types
- Use zero values as "not provided" sentinel values
- Fix struct comments.
- Replace strings.Builder with fmt.Fprintf
- Use url.Parse instead of strings.Split for URI parsing
- Use slices.Collect(maps.Values()) for cleaner code

Concurrency fixes:
- Implement Copy-on-Write pattern to fix race conditions
- Add Version field for optimistic concurrency control
- Create CompareAndSwap method for atomic updates
- Ensure thread-safe session modifications

Tool creation:
- Replace NewServerTool with manual tool creation
- Avoid output schemas to prevent structured output requirement
- Add explicit type conversions for JSON unmarshaling

Documentation:
- Remove Testing section from README per feedback
- Clarify that Thought.Index is per-session, not global
- Add godoc comments for all exported types and functions
Copy link
Author

@nsega nsega left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for your feedback. I addressed the feedback. Could you please take a look at it again at your convenience?

Thought string `json:"thought"`
NextNeeded *bool `json:"nextNeeded,omitempty"`
ReviseStep *int `json:"reviseStep,omitempty"`
CreateBranch *bool `json:"createBranch,omitempty"`
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The defaults I expected are actually:

  • NextNeeded : default behavior is true for continue thinking.
  • CreateBranch : default behavior is false for don't create a branch.

Since only CreateBranch doesn't need a pointer in this case, I updated the field from *bool to bool.


sessionID := args.SessionID
if sessionID == "" {
sessionID = fmt.Sprintf("session_%d", time.Now().Unix())
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your suggestion. Since I didn't know the randText function, I am glad to have found a way to generate sufficient uniqueness. I copied randText() from util.go.

session := &ThinkingSession{
ID: sessionID,
Problem: args.Problem,
Thoughts: []Thought{},
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're totally right. Just omitted the field.

ID: sessionID,
Problem: args.Problem,
Thoughts: []Thought{},
CurrentThought: 0,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also you're right. Just omitted the field.

Status: "active",
Created: time.Now(),
LastActivity: time.Now(),
Branches: []string{},
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just omitted the field, too.

}
}

func (s *SessionStore) GetSession(id string) (*ThinkingSession, bool) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the Get by following idiomatic Go.

s.sessions[session.ID] = session
}

func (s *SessionStore) ListSessions() []*ThinkingSession {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the function name to Sessions.

func (s *SessionStore) ListSessions() []*ThinkingSession {
s.mu.RLock()
defer s.mu.RUnlock()
sessions := make([]*ThinkingSession, 0, len(s.sessions))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the part to use slices.Clone.


var store = NewSessionStore()

// Tool argument structures
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the doc for each struct respectively.


// Add thinking tools
server.AddTools(
mcp.NewServerTool(
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your explanation. I understand the current status of the PR you're working on.

Yes, I can. I tried to implement these tools without NewServerTool. As I may have missed something, I would appreciate it if you could provide me with further feedback on the implementation.

@nsega nsega requested a review from jba June 27, 2025 11:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants