MCP go-sdk implementation Gap-1: provide APIs to bind event storage with user identifiers
Issue Summary
The MCP Go SDK's default session ID generation mechanism does not comply with the MCP specification's security best practices for session management.
Current Implementation Analysis
1. Session ID Generation Function
// go-sdk-main/mcp/util.go:22-30
func randText() string {
// ⌈log₃₂ 2¹²⁸⌉ = 26 chars
src := make([]byte, 26)
rand.Read(src)
for i := range src {
src[i] = base32alphabet[src[i]%32]
}
return string(src)
}
// go-sdk-main/mcp/util.go:20
const base32alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
2. Default Assignment Logic
// go-sdk-main/mcp/server.go:129-131
if opts.GetSessionID == nil {
opts.GetSessionID = randText
}
3. Actual Call Path
// go-sdk-main/mcp/streamable.go:221-225
if sessionID == "" {
// In stateless mode, sessionID may be nonempty even if there's no
// existing transport.
sessionID = server.opts.GetSessionID()
}
Complete Call Chain
HTTP Request → ServeHTTP()
↓
Check Mcp-Session-Id header
↓
If empty → server.opts.GetSessionID()
↓
Call randText() function
↓
Generate 26-character Base32 random string
Generated Session ID Characteristics
Format: 26-character Base32 encoded string
Character Set: ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
Length: 26 characters
Example: ABCDEFGHIJKLMNOPQRSTUVWXYZ or 2ABCDEFGHIJKLMNOPQRSTUVWX
MCP Specification Requirement
According to the MCP specification's security best practices:
MCP servers SHOULD bind session IDs to user-specific information.
When storing or transmitting session-related data (e.g., in a queue), combine the session ID with information unique to the authorized user, such as their internal user ID. Use a key format like
<user_id>:<session_id>. This ensures that even if an attacker guesses a session ID, they cannot impersonate another user as the user ID is derived from the user token and not provided by the client.
Compliance Analysis
Current Implementation:
- No user ID binding
-
Does not follow
<user_id>:<session_id>format
Specification Requirement:
- Format:
<user_id>:<session_id> - Example:
user123:ABCDEFGHIJKLMNOPQRSTUVWXYZ
Recommendation
** One Possible Solutions**
- Modify Default Implementation:
func (s *Server) generateSecureSessionID(userID string) string {
sessionID := randText()
return fmt.Sprintf("%s:%s", userID, sessionID)
}
- Add Security Configuration Options:
type ServerOptions struct {
// Existing fields...
// New: Secure session ID generator
SecureSessionIDGenerator func(userID string) string
// New: User ID extractor from auth context
UserIDExtractor func(ctx context.Context) (string, error)
}
There seems to be some confusion here (possibly ours). We understand that requirement as something that applications must carry out, not the SDK itself. You’ll note that it talks about combining the session ID with something else, which assumes that there already is a session ID. Furthermore, nothing in the spec requires that a user ID or other user specific data is available.
@jba Thanks for your response!
I understand your point that the specification doesn’t explicitly require the SDK to handle user ID binding.
However, in practice, most developers rely heavily on the SDK’s default behavior. If the default implementation does not follow the spec’s specification, it’s very likely that many real-world deployments will also not follow it. My point is more about incentives and defaults. If the official SDK itself doesn’t provide (or enforce) a compliant default, then it’s unrealistic to expect end users (application developers) to do so manually.
In contrast, as an official SDK, I believe that if the SDK itself provides a secure default implementation that follows the MCP specification, it will offer users an easier way to follow the MCP specification. This way, "follow the rules" isn't left to chance.
Looking forward to your reply. I will be happy to have a further discussion with you.
Sorry, I don't understand. Rereading the spec, I don't think there's a problem with our default session ID generation, though we may need to expose more secure APIs for resumability.
The spec says that session IDs should be cryptographic text. That's consistent with what we do, and what the typescript sdk recommends: https://github.com/modelcontextprotocol/typescript-sdk/blob/c84ef24215396393f28b40bc5ac4cc6ea8fd7b2d/src/server/streamableHttp.ts#L122
The section of the spec you're citing is referring to storing data associated with a session, and combining it with user data. That's largely up to the server author to enforce, with one exception: we should probably provide an implementation of the event store that binds events with user data.
@findleyr Thanks for your reply! After carefully reading the MCP specification, I believe the two most relevant sections are:
-
“MCP servers SHOULD bind session IDs to user-specific information. When storing or transmitting session-related data (e.g., in a queue), combine the session ID with information unique to the authorized user, such as their internal user ID. Use a key format like <user_id>:<session_id>.”
-
“The session ID SHOULD be globally unique and cryptographically secure (e.g., a securely generated UUID, a JWT, or a cryptographic hash).” (This is what the Go and TypeScript SDKs currently follow.)
So it seems like there are two separate security requirements:
✅ The session ID itself should be cryptographically secure. (well implemented)
⚠️ The session ID should be bound to the user identity to prevent impersonation. (not implemented)
Currently, both the Go SDK and the TS SDK you provide look only achieve (1). There’s no built-in way to support (2), which means that unless developers manually implement it, this part of the spec won’t be followed in practice. That's the reason I post this issue, making the SDK follow all specifications requested by the MCP protocol.
Looking forward to your reply.
I propose to address this with #586.
Nevermind, we're not sure how to do this, as many or most authentication schemes don't have a concept of user identifier. Advice is welcomed.
In #580, we propose disabling replayability by default, which should cut off one form of hijacking with the default configuration. It is still up to the user to ensure that server-side authentication occurs on every request, and not rely on the session ID for identity.
Jonathan's #589 is a better solution. If that lands, we'll use it to join session information with the user identifier.
Thanks for your reply @findleyr. Sorry for the delay because I had surgery two weeks ago and I couldn't go back to work until now. I have read the #589. I think it's a good solution for this issue. Please feel free to let me know if you need me to do something