-
Notifications
You must be signed in to change notification settings - Fork 702
feat: add SessionWithResourceTemplates for session-specific resource templates #624
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
Conversation
β¦templates Implements session-specific resource templates to achieve parity with SessionWithTools and SessionWithResources. This allows sessions to have their own resource templates that override global templates with the same URI pattern. Key changes: - Add SessionWithResourceTemplates interface to ClientSession hierarchy - Implement interface in both SSE and StreamableHTTP transports - Add AddSessionResourceTemplate(s) and DeleteSessionResourceTemplates methods - Update handleListResourceTemplates to merge session and global templates - Update handleReadResource to check session templates before global ones - Session templates trigger notifications/resources/list_changed when modified Closes #622
WalkthroughAdds session-scoped resource template support: new Changes
Estimated code review effortπ― 4 (Complex) | β±οΈ ~50 minutes Potential hotspots to review:
Possibly related PRs
Suggested labels
Pre-merge checks and finishing touchesβ Failed checks (1 warning)
β Passed checks (4 passed)
β¨ Finishing touches
π§ͺ Generate unit tests (beta)
π Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro π Files selected for processing (3)
π§° Additional context usedπ Path-based instructions (2)**/*.goπ CodeRabbit inference engine (AGENTS.md)
Files:
**/*_test.goπ CodeRabbit inference engine (AGENTS.md)
Files:
𧬠Code graph analysis (3)server/server.go (2)
server/session_resource_templates_test.go (6)
server/session.go (4)
π Additional comments (10)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
π§Ή Nitpick comments (10)
server/errors.go (1)
16-22: Align error message with existing βper-sessionβ phrasingKeep wording consistent with ErrSessionDoesNotSupportTools/Resources.
-ErrSessionDoesNotSupportResourceTemplates = errors.New("session does not support resource templates") +ErrSessionDoesNotSupportResourceTemplates = errors.New("session does not support per-session resource templates")server/streamable_http_sampling_test.go (2)
27-36: Add interface assertion for SessionWithResourceTemplatesGuard the new constructor param with a compile-time-style assertion, mirroring the sampling check.
// Verify it implements SessionWithSampling _, ok := any(session).(SessionWithSampling) if !ok { t.Error("streamableHttpSession should implement SessionWithSampling") } + + // Verify it implements SessionWithResourceTemplates + if _, ok := any(session).(SessionWithResourceTemplates); !ok { + t.Error("streamableHttpSession should implement SessionWithResourceTemplates") + }
140-148: Also assert SessionWithResourceTemplates in the interface testMirror the sampling assertion to cover the new capability.
// Verify it implements SessionWithSampling _, ok := any(session).(SessionWithSampling) if !ok { t.Error("streamableHttpSession should implement SessionWithSampling") } + + // Verify it implements SessionWithResourceTemplates + if _, ok := any(session).(SessionWithResourceTemplates); !ok { + t.Error("streamableHttpSession should implement SessionWithResourceTemplates") + }server/sse.go (1)
31-35: Clarify key semantics for resourceTemplatesNote that the map key is the templateβs URITemplate.Raw() string for consistency with server/session.go.
server/server.go (1)
883-910: Merge precedence is correct; align sort style with resourcesSession templates override globals; good. Optionally use slices.SortedFunc like handleListResources for consistency.
- // Convert map to slice for sorting and pagination - templates := make([]mcp.ResourceTemplate, 0, len(templateMap)) - for _, template := range templateMap { - templates = append(templates, template) - } - sort.Slice(templates, func(i, j int) bool { - return templates[i].Name < templates[j].Name - }) + // Convert map to slice and sort by name (consistent with resources) + templates := slices.SortedFunc(maps.Values(templateMap), func(a, b mcp.ResourceTemplate) int { + return cmp.Compare(a.Name, b.Name) + })server/session.go (1)
695-751: Clarify expected identifier for deletionDocument that uriTemplates must be the URITemplate.Raw() string; optional helper to delete by mcp.ResourceTemplate could reduce footguns.
Happy to add a DeleteSessionResourceTemplateByTemplate(sessionID string, t mcp.ResourceTemplate) wrapper if helpful.
server/streamable_http.go (4)
624-631: Cleanup includes per-session templatesTemplates store is cleared with other session-scoped data. Consider fixing minor typos in comments (βrelateddataβ, βrequstIDβ) later.
840-872: Thread-safe store mirrors existing patterns; optional simplificationImplementation is correct and race-safe. Optionally, simplify cloning for readability:
- cloned := make(map[string]ServerResourceTemplate, len(s.templates[sessionID])) - maps.Copy(cloned, s.templates[sessionID]) - return cloned + src := s.templates[sessionID] + cloned := maps.Clone(src) + if cloned == nil { + cloned = make(map[string]ServerResourceTemplate) + } + return cloned
1004-1011: Interface conformance achieved with safe Get/SetGet/Set delegate to the store, preserving copy-on-get semantics. Assertions cover SessionWithResourceTemplates. If helpful, add a brief comment noting Set replaces the entire map (callers should use server-level Add/Delete helpers for incremental updates).
Also applies to: 1013-1017
521-527: Send notification by value, not pointerAvoid taking the address of the select-scoped variable; just send the value:
- case writeChan <- &nt: + case writeChan <- nt:This reduces escapes and keeps semantics consistent with POST path.
If you prefer to keep pointers for large payloads, confirm thereβs measurable benefit and no unintended escapes in benchmarks.
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
π Files selected for processing (6)
server/errors.go(1 hunks)server/server.go(2 hunks)server/session.go(2 hunks)server/sse.go(3 hunks)server/streamable_http.go(9 hunks)server/streamable_http_sampling_test.go(3 hunks)
π§° Additional context used
π Path-based instructions (2)
**/*.go
π CodeRabbit inference engine (AGENTS.md)
**/*.go: Order imports: standard library first, then third-party, then local packages (goimports enforces this)
Follow Go naming conventions: exported identifiers in PascalCase; unexported in camelCase; acronyms uppercase (HTTP, JSON, MCP)
Error handling: return sentinel errors, wrap with fmt.Errorf("context: %w", err), and check with errors.Is/As
Prefer explicit types and strongly-typed structs; avoid using any except where protocol flexibility is required (e.g., Arguments any)
All exported types and functions must have GoDoc comments starting with the identifier name; avoid inline comments unless necessary
Functions that are handlers or long-running must accept context.Context as the first parameter
Ensure thread safety for shared state using sync.Mutex and document thread-safety requirements in comments
For JSON: use json struct tags with omitempty for optional fields; use json.RawMessage for flexible/deferred parsing
Files:
server/session.goserver/sse.goserver/streamable_http_sampling_test.goserver/errors.goserver/server.goserver/streamable_http.go
**/*_test.go
π CodeRabbit inference engine (AGENTS.md)
**/*_test.go: Testing: use testify/assert and testify/require
Write table-driven tests using a tests := []struct{ name, ... } pattern
Go test files must end with _test.go
Files:
server/streamable_http_sampling_test.go
𧬠Code graph analysis (4)
server/session.go (4)
server/server.go (3)
ServerResourceTemplate(73-76)MCPServer(142-169)ResourceTemplateHandlerFunc(37-37)mcp/types.go (2)
ResourceTemplate(709-728)URITemplate(77-79)server/errors.go (2)
ErrSessionNotFound(16-16)ErrSessionDoesNotSupportResourceTemplates(21-21)server/hooks.go (1)
Hooks(94-121)
server/sse.go (2)
server/server.go (1)
ServerResourceTemplate(73-76)server/session.go (2)
ClientSession(12-21)SessionWithResourceTemplates(55-63)
server/server.go (2)
mcp/types.go (3)
ResourceTemplate(709-728)Params(180-180)URITemplate(77-79)server/session.go (2)
ClientSessionFromContext(105-110)SessionWithResourceTemplates(55-63)
server/streamable_http.go (2)
server/server.go (2)
MCPServer(142-169)ServerResourceTemplate(73-76)server/session.go (3)
SessionWithTools(33-41)SessionWithResources(44-52)SessionWithResourceTemplates(55-63)
π Additional comments (9)
server/streamable_http_sampling_test.go (1)
180-183: Constructor call with nil stores looks finePassing nil for the new store in this queue-full test maintains behavior. No change needed.
server/sse.go (2)
101-121: Thread-safe copy/setter pattern LGTMRange to copy and Clear+Store keep per-session templates safe for concurrent access.
169-176: Interface conformance marker LGTMsseSession now explicitly implements SessionWithResourceTemplates.
server/session.go (2)
54-63: Interface shape LGTMThread-safe getters/setters match existing patterns for tools/resources.
628-635: Convenience wrapper LGTMDelegates to AddSessionResourceTemplates as expected.
server/streamable_http.go (4)
143-149: Store wiring for session-scoped templates looks correctField added and initialized consistently with existing stores; thread-safety approach matches tools/resources. No blockers.
Also applies to: 168-176
350-351: Constructor injection covers both ephemeral (POST) and persistent (GET) sessionsPassing the shared templates store into newStreamableHttpSession ensures session-scoped template ops work across transports and lifecycles. Good use of LoadOrStore to avoid TOCTOU on GET.
Also applies to: 485-487
934-936: Session gains templates store; constructor wiring is consistentField and constructor param added without altering existing behavior. No issues.
Also applies to: 947-953
143-149: <!-- [scratchpad_start]
[task review_verification 1/1]
Verification complete - full handler logic confirmed.[observations]
handleListResourceTemplates (lines 878-920):
- Global templates loaded first into templateMap
- Session templates checked and merged with override semantics
- Result: session templates override global ones via map assignment
- Verified at line 903:
templateMap[uriTemplate] = serverTemplate.TemplatehandleReadResource (lines 936-1050):
- Direct handlers: session checked first (945-953), global fallback (956-961)
- Template matching: session templates checked first (998-1013), with break on match
- Global template fallback: lines 1015-1028 - "If not found in session templates, check global templates"
- Both flows have precedence: session > global
- Merge behavior: session takes priority, global is fallback
[analysis]
- Session-level precedence confirmed in both list and read handlers
- Merge logic verified: session overrides global templates
- Fallback pattern consistent across handlers
- Code matches PR goals exactly
- No issues found
[done]
Verification confirms review comment request is satisfied by current code implementation.
</scratchpad_end] -->Session template precedence and merging verifiedβno issues found
The code correctly implements session-level precedence:
- handleListResourceTemplates: Global templates loaded, then session templates merged with override semantics (line 903:
templateMap[uriTemplate] = serverTemplate.Template)- handleReadResource: Session templates checked first (lines 998β1013), global templates checked only if no match found (lines 1015β1028)
Both handlers follow consistent patterns with explicit comments documenting the merge and precedence logic. The implementation satisfies PR goals.
- Add sessionTestClientWithResourceTemplates mock - Test AddSessionResourceTemplate and AddSessionResourceTemplates - Test DeleteSessionResourceTemplates - Test session template override behavior - Test notification behavior (enabled/disabled) - Test uninitialized session handling - Test error cases for unsupported sessions - Verify thread-safety through existing patterns Coverage increased from 70.17% to 72.8% (+2.63%)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
π§Ή Nitpick comments (7)
server/session_resource_templates_test.go (7)
33-39: Protect initialized flag to avoid data races (tests under -race).Use atomic.Bool (or guard with the existing mutex). Keeps tests race-free when server goroutines read it.
type sessionTestClientWithResourceTemplates struct { - sessionID string - notificationChannel chan mcp.JSONRPCNotification - initialized bool + sessionID string + notificationChannel chan mcp.JSONRPCNotification + initialized atomic.Bool sessionResourceTemplates map[string]ServerResourceTemplate mu sync.RWMutex } func (f *sessionTestClientWithResourceTemplates) Initialize() { - f.initialized = true + f.initialized.Store(true) } func (f *sessionTestClientWithResourceTemplates) Initialized() bool { - return f.initialized + return f.initialized.Load() }Remember to import sync/atomic in the stdlib section.
41-52: Simplify GetSessionResourceTemplates with maps.Clone.Removes nil checks and manual allocation.
func (f *sessionTestClientWithResourceTemplates) GetSessionResourceTemplates() map[string]ServerResourceTemplate { f.mu.RLock() defer f.mu.RUnlock() - - if f.sessionResourceTemplates == nil { - return nil - } - - templatesCopy := make(map[string]ServerResourceTemplate, len(f.sessionResourceTemplates)) - maps.Copy(templatesCopy, f.sessionResourceTemplates) - return templatesCopy + return maps.Clone(f.sessionResourceTemplates) }
54-66: Likewise, Clone on SetSessionResourceTemplates.Less code, same semantics.
func (f *sessionTestClientWithResourceTemplates) SetSessionResourceTemplates(templates map[string]ServerResourceTemplate) { f.mu.Lock() defer f.mu.Unlock() - - if templates == nil { - f.sessionResourceTemplates = nil - return - } - - templatesCopy := make(map[string]ServerResourceTemplate, len(templates)) - maps.Copy(templatesCopy, templates) - f.sessionResourceTemplates = templatesCopy + f.sessionResourceTemplates = maps.Clone(templates) }
239-245: Reduce flakiness in notification waits.Replace repeated select+time.After with a small helper or assert/require.Eventually and slightly longer overall timeout.
Add a helper once:
func expectNotification(t *testing.T, ch <-chan mcp.JSONRPCNotification, want string, d time.Duration) mcp.JSONRPCNotification { t.Helper() select { case n := <-ch: require.Equal(t, want, n.Method) return n case <-time.After(d): t.Fatalf("timeout waiting for %s", want) return mcp.JSONRPCNotification{} } }Then call:
_ = expectNotification(t, sessionChan, "notifications/resources/list_changed", 500*time.Millisecond)Alternatively, use require.Eventually with a non-busy poll.
Also applies to: 276-282, 317-325, 339-350, 389-399, 416-421, 512-517, 563-567, 575-579
475-483: Guard result before indexing and assert type.Avoid panics and improve failure signals.
response := server.HandleMessage(sessionCtx, requestBytes) resp, ok := response.(mcp.JSONRPCResponse) require.True(t, ok) - readResourceResult, ok := resp.Result.(mcp.ReadResourceResult) - assert.True(t, ok) - - if text := readResourceResult.Contents[0].(mcp.TextResourceContents).Text; text != "session result" { - t.Errorf("Expected result 'session result', got %q", text) - } + readResourceResult, ok := resp.Result.(mcp.ReadResourceResult) + require.True(t, ok) + require.Len(t, readResourceResult.Contents, 1) + tc, ok := readResourceResult.Contents[0].(mcp.TextResourceContents) + require.True(t, ok) + assert.Equal(t, "session result", tc.Text)
127-218: Add read-path assertions for global usage and fallback after delete.
- After listing, also read test://another/{id} to confirm global handler is used.
- After deleting a session template, read the same URI to confirm fallback to global.
I can draft targeted tests mirroring the existing HandleMessage patterns if you want them added.
Also applies to: 487-522
70-582: Consider table-driven tests and t.Parallel for similar cases.Several tests follow the same add/delete/notify pattern. Table-driven subtests with t.Parallel would cut duplication and speed runs.
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
π Files selected for processing (1)
server/session_resource_templates_test.go(1 hunks)
π§° Additional context used
π Path-based instructions (2)
**/*.go
π CodeRabbit inference engine (AGENTS.md)
**/*.go: Order imports: standard library first, then third-party, then local packages (goimports enforces this)
Follow Go naming conventions: exported identifiers in PascalCase; unexported in camelCase; acronyms uppercase (HTTP, JSON, MCP)
Error handling: return sentinel errors, wrap with fmt.Errorf("context: %w", err), and check with errors.Is/As
Prefer explicit types and strongly-typed structs; avoid using any except where protocol flexibility is required (e.g., Arguments any)
All exported types and functions must have GoDoc comments starting with the identifier name; avoid inline comments unless necessary
Functions that are handlers or long-running must accept context.Context as the first parameter
Ensure thread safety for shared state using sync.Mutex and document thread-safety requirements in comments
For JSON: use json struct tags with omitempty for optional fields; use json.RawMessage for flexible/deferred parsing
Files:
server/session_resource_templates_test.go
**/*_test.go
π CodeRabbit inference engine (AGENTS.md)
**/*_test.go: Testing: use testify/assert and testify/require
Write table-driven tests using a tests := []struct{ name, ... } pattern
Go test files must end with _test.go
Files:
server/session_resource_templates_test.go
𧬠Code graph analysis (1)
server/session_resource_templates_test.go (6)
mcp/types.go (8)
JSONRPCNotification(335-338)ReadResourceRequest(612-616)ResourceContents(737-739)Params(180-180)ListResourceTemplatesResult(605-608)ResourceTemplate(709-728)URITemplate(77-79)ReadResourceResult(628-631)server/server.go (2)
ServerResourceTemplate(73-76)WithResourceCapabilities(205-213)server/session.go (2)
SessionWithResourceTemplates(55-63)ClientSessionFromContext(105-110)mcp/resources.go (1)
NewResourceTemplate(60-71)server/hooks.go (1)
Hooks(94-121)server/errors.go (1)
ErrSessionDoesNotSupportResourceTemplates(21-21)
β° Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: coverage
- GitHub Check: test
π Additional comments (1)
server/session_resource_templates_test.go (1)
3-15: Go toolchain version confirmed.The go.mod declares
go 1.23.0, which satisfies the minimum requirement (go 1.21+) for using themapsstdlib package. No changes needed.
- Use atomic.Bool for initialized field in test mock to prevent data races - Add nil checks for URITemplate in handleReadResource for both session and global templates - Add validation in AddSessionResourceTemplates to prevent nil URITemplate, empty URI, or empty Name - Simplify Get/SetSessionResourceTemplates using maps.Clone - Fix test initialization states to match expected behavior All tests pass with race detector.
Merging this branch will decrease overall coverage
Coverage by fileChanged files (no unit tests)
Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code. Changed unit test files
|
Description
Implements session-specific resource templates to achieve parity with existing
SessionWithToolsandSessionWithResourcesinterfaces. This allows individual sessions to have their own resource templates that override global templates with the same URI pattern.Fixes #622
Type of Change
Implementation Details
Core Components
ClientSessionwith methods to get/set session-specific resource templatesAddSessionResourceTemplate(s)andDeleteSessionResourceTemplatesmethods following the pattern of existing session management APIsKey Features
handleListResourceTemplatesmerges session and global templateshandleReadResourcechecks session templates before global templatesnotifications/resources/list_changedwhen session templates are modifiedsync.Map(SSE) and centralizedsync.RWMutexstore (HTTP) for concurrent accessFiles Changed
server/session.go: Interface definition and session management methodsserver/errors.go: New error constant for unsupported sessionsserver/sse.go: SSE transport implementation usingsync.Mapserver/streamable_http.go: StreamableHTTP transport using centralized store patternserver/server.go: Resource handler integration for listing and readingserver/streamable_http_sampling_test.go: Updated test callsChecklist
MCP Spec Compliance
Testing
All automated verification passed:
go build ./...golangci-lint run(0 issues)go test ./serverTest Coverage
sync.Mapandsync.RWMutexpatternsSessionWithToolsandSessionWithResourcesAdditional Information
This implementation resolves the issue where users could not add resource templates at the session level because URI validation rejected RFC 6570 template syntax. Session-specific resource templates now work identically to session-specific tools and resources, providing a consistent API experience.
Summary by CodeRabbit
New Features
Bug Fixes / Behavior
Tests