Skip to content

Commit ef80a50

Browse files
authored
feat: add resource handler middleware capability (#569)
* feat: add resource handler middleware capability This is a fairly direct adaption of the existing tool handler middleware to also allow support for resource middlewares. Use case: I'm working on an MCP server that manages an API client that is used for both tool and resource calls. The tool handler middleware provides a nice pattern to wrap tool calls that fits some use cases better than the before/after tool call hooks. It would be helpful to have first party support for this pattern in the library so I don't need to work around it with custom closures etc. Notes: - There are currently (that I can find) that exercise the existing tool handler middleware logic, so I did not add tests for the resource handler middleware logic. - Existing docs, specifically those for the streamable HTTP transport, reference some middleware functions (for both tools and resources) that don't exist (ex: `s.AddToolMiddleware` does not, `s.AddResourceMiddleware` does not exist, etc). It seems they may be out of date. Happy to discuss updates to docs in a separate PR. Signed-off-by: TJ Hoplock <[email protected]> * feat: add `WithResourceRecovery()` ServerOption The existing `WithRecovery()` ServerOption is tool oriented, this adds a corresponding recovery handler for resources. This will be especially useful if Resource middlewares are used, where things may possibly/need to panic. Signed-off-by: TJ Hoplock <[email protected]> --------- Signed-off-by: TJ Hoplock <[email protected]>
1 parent b924391 commit ef80a50

File tree

1 file changed

+70
-24
lines changed

1 file changed

+70
-24
lines changed

server/server.go

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ type ToolHandlerFunc func(ctx context.Context, request mcp.CallToolRequest) (*mc
4343
// ToolHandlerMiddleware is a middleware function that wraps a ToolHandlerFunc.
4444
type ToolHandlerMiddleware func(ToolHandlerFunc) ToolHandlerFunc
4545

46+
// ResourceHandlerMiddleware is a middleware function that wraps a ResourceHandlerFunc.
47+
type ResourceHandlerMiddleware func(ResourceHandlerFunc) ResourceHandlerFunc
48+
4649
// ToolFilterFunc is a function that filters tools based on context, typically using session information.
4750
type ToolFilterFunc func(ctx context.Context, tools []mcp.Tool) []mcp.Tool
4851

@@ -151,21 +154,22 @@ type MCPServer struct {
151154
capabilitiesMu sync.RWMutex
152155
toolFiltersMu sync.RWMutex
153156

154-
name string
155-
version string
156-
instructions string
157-
resources map[string]resourceEntry
158-
resourceTemplates map[string]resourceTemplateEntry
159-
prompts map[string]mcp.Prompt
160-
promptHandlers map[string]PromptHandlerFunc
161-
tools map[string]ServerTool
162-
toolHandlerMiddlewares []ToolHandlerMiddleware
163-
toolFilters []ToolFilterFunc
164-
notificationHandlers map[string]NotificationHandlerFunc
165-
capabilities serverCapabilities
166-
paginationLimit *int
167-
sessions sync.Map
168-
hooks *Hooks
157+
name string
158+
version string
159+
instructions string
160+
resources map[string]resourceEntry
161+
resourceTemplates map[string]resourceTemplateEntry
162+
prompts map[string]mcp.Prompt
163+
promptHandlers map[string]PromptHandlerFunc
164+
tools map[string]ServerTool
165+
toolHandlerMiddlewares []ToolHandlerMiddleware
166+
resourceHandlerMiddlewares []ResourceHandlerMiddleware
167+
toolFilters []ToolFilterFunc
168+
notificationHandlers map[string]NotificationHandlerFunc
169+
capabilities serverCapabilities
170+
paginationLimit *int
171+
sessions sync.Map
172+
hooks *Hooks
169173
}
170174

171175
// WithPaginationLimit sets the pagination limit for the server.
@@ -223,6 +227,36 @@ func WithToolHandlerMiddleware(
223227
}
224228
}
225229

230+
// WithResourceHandlerMiddleware allows adding a middleware for the
231+
// resource handler call chain.
232+
func WithResourceHandlerMiddleware(
233+
resourceHandlerMiddleware ResourceHandlerMiddleware,
234+
) ServerOption {
235+
return func(s *MCPServer) {
236+
s.middlewareMu.Lock()
237+
s.resourceHandlerMiddlewares = append(s.resourceHandlerMiddlewares, resourceHandlerMiddleware)
238+
s.middlewareMu.Unlock()
239+
}
240+
}
241+
242+
// WithResourceRecovery adds a middleware that recovers from panics in resource handlers.
243+
func WithResourceRecovery() ServerOption {
244+
return WithResourceHandlerMiddleware(func(next ResourceHandlerFunc) ResourceHandlerFunc {
245+
return func(ctx context.Context, request mcp.ReadResourceRequest) (result []mcp.ResourceContents, err error) {
246+
defer func() {
247+
if r := recover(); r != nil {
248+
err = fmt.Errorf(
249+
"panic recovered in %s resource handler: %v",
250+
request.Params.URI,
251+
r,
252+
)
253+
}
254+
}()
255+
return next(ctx, request)
256+
}
257+
})
258+
}
259+
226260
// WithToolFilter adds a filter function that will be applied to tools before they are returned in list_tools
227261
func WithToolFilter(
228262
toolFilter ToolFilterFunc,
@@ -301,14 +335,16 @@ func NewMCPServer(
301335
opts ...ServerOption,
302336
) *MCPServer {
303337
s := &MCPServer{
304-
resources: make(map[string]resourceEntry),
305-
resourceTemplates: make(map[string]resourceTemplateEntry),
306-
prompts: make(map[string]mcp.Prompt),
307-
promptHandlers: make(map[string]PromptHandlerFunc),
308-
tools: make(map[string]ServerTool),
309-
name: name,
310-
version: version,
311-
notificationHandlers: make(map[string]NotificationHandlerFunc),
338+
resources: make(map[string]resourceEntry),
339+
resourceTemplates: make(map[string]resourceTemplateEntry),
340+
prompts: make(map[string]mcp.Prompt),
341+
promptHandlers: make(map[string]PromptHandlerFunc),
342+
tools: make(map[string]ServerTool),
343+
toolHandlerMiddlewares: make([]ToolHandlerMiddleware, 0),
344+
resourceHandlerMiddlewares: make([]ResourceHandlerMiddleware, 0),
345+
name: name,
346+
version: version,
347+
notificationHandlers: make(map[string]NotificationHandlerFunc),
312348
capabilities: serverCapabilities{
313349
tools: nil,
314350
resources: nil,
@@ -838,7 +874,17 @@ func (s *MCPServer) handleReadResource(
838874
if entry, ok := s.resources[request.Params.URI]; ok {
839875
handler := entry.handler
840876
s.resourcesMu.RUnlock()
841-
contents, err := handler(ctx, request)
877+
878+
finalHandler := handler
879+
s.middlewareMu.RLock()
880+
mw := s.resourceHandlerMiddlewares
881+
// Apply middlewares in reverse order
882+
for i := len(mw) - 1; i >= 0; i-- {
883+
finalHandler = mw[i](finalHandler)
884+
}
885+
s.middlewareMu.RUnlock()
886+
887+
contents, err := finalHandler(ctx, request)
842888
if err != nil {
843889
return nil, &requestError{
844890
id: id,

0 commit comments

Comments
 (0)