server.go

   1// Package server provides MCP (Model Context Protocol) server implementations.
   2package server
   3
   4import (
   5	"context"
   6	"encoding/base64"
   7	"encoding/json"
   8	"fmt"
   9	"slices"
  10	"sort"
  11	"sync"
  12
  13	"github.com/mark3labs/mcp-go/mcp"
  14)
  15
  16// resourceEntry holds both a resource and its handler
  17type resourceEntry struct {
  18	resource mcp.Resource
  19	handler  ResourceHandlerFunc
  20}
  21
  22// resourceTemplateEntry holds both a template and its handler
  23type resourceTemplateEntry struct {
  24	template mcp.ResourceTemplate
  25	handler  ResourceTemplateHandlerFunc
  26}
  27
  28// ServerOption is a function that configures an MCPServer.
  29type ServerOption func(*MCPServer)
  30
  31// ResourceHandlerFunc is a function that returns resource contents.
  32type ResourceHandlerFunc func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error)
  33
  34// ResourceTemplateHandlerFunc is a function that returns a resource template.
  35type ResourceTemplateHandlerFunc func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error)
  36
  37// PromptHandlerFunc handles prompt requests with given arguments.
  38type PromptHandlerFunc func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error)
  39
  40// ToolHandlerFunc handles tool calls with given arguments.
  41type ToolHandlerFunc func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error)
  42
  43// ToolHandlerMiddleware is a middleware function that wraps a ToolHandlerFunc.
  44type ToolHandlerMiddleware func(ToolHandlerFunc) ToolHandlerFunc
  45
  46// ToolFilterFunc is a function that filters tools based on context, typically using session information.
  47type ToolFilterFunc func(ctx context.Context, tools []mcp.Tool) []mcp.Tool
  48
  49// ServerTool combines a Tool with its ToolHandlerFunc.
  50type ServerTool struct {
  51	Tool    mcp.Tool
  52	Handler ToolHandlerFunc
  53}
  54
  55// ServerPrompt combines a Prompt with its handler function.
  56type ServerPrompt struct {
  57	Prompt  mcp.Prompt
  58	Handler PromptHandlerFunc
  59}
  60
  61// ServerResource combines a Resource with its handler function.
  62type ServerResource struct {
  63	Resource mcp.Resource
  64	Handler  ResourceHandlerFunc
  65}
  66
  67// serverKey is the context key for storing the server instance
  68type serverKey struct{}
  69
  70// ServerFromContext retrieves the MCPServer instance from a context
  71func ServerFromContext(ctx context.Context) *MCPServer {
  72	if srv, ok := ctx.Value(serverKey{}).(*MCPServer); ok {
  73		return srv
  74	}
  75	return nil
  76}
  77
  78// UnparsableMessageError is attached to the RequestError when json.Unmarshal
  79// fails on the request.
  80type UnparsableMessageError struct {
  81	message json.RawMessage
  82	method  mcp.MCPMethod
  83	err     error
  84}
  85
  86func (e *UnparsableMessageError) Error() string {
  87	return fmt.Sprintf("unparsable %s request: %s", e.method, e.err)
  88}
  89
  90func (e *UnparsableMessageError) Unwrap() error {
  91	return e.err
  92}
  93
  94func (e *UnparsableMessageError) GetMessage() json.RawMessage {
  95	return e.message
  96}
  97
  98func (e *UnparsableMessageError) GetMethod() mcp.MCPMethod {
  99	return e.method
 100}
 101
 102// RequestError is an error that can be converted to a JSON-RPC error.
 103// Implements Unwrap() to allow inspecting the error chain.
 104type requestError struct {
 105	id   any
 106	code int
 107	err  error
 108}
 109
 110func (e *requestError) Error() string {
 111	return fmt.Sprintf("request error: %s", e.err)
 112}
 113
 114func (e *requestError) ToJSONRPCError() mcp.JSONRPCError {
 115	return mcp.JSONRPCError{
 116		JSONRPC: mcp.JSONRPC_VERSION,
 117		ID:      mcp.NewRequestId(e.id),
 118		Error: struct {
 119			Code    int    `json:"code"`
 120			Message string `json:"message"`
 121			Data    any    `json:"data,omitempty"`
 122		}{
 123			Code:    e.code,
 124			Message: e.err.Error(),
 125		},
 126	}
 127}
 128
 129func (e *requestError) Unwrap() error {
 130	return e.err
 131}
 132
 133// NotificationHandlerFunc handles incoming notifications.
 134type NotificationHandlerFunc func(ctx context.Context, notification mcp.JSONRPCNotification)
 135
 136// MCPServer implements a Model Context Protocol server that can handle various types of requests
 137// including resources, prompts, and tools.
 138type MCPServer struct {
 139	// Separate mutexes for different resource types
 140	resourcesMu            sync.RWMutex
 141	promptsMu              sync.RWMutex
 142	toolsMu                sync.RWMutex
 143	middlewareMu           sync.RWMutex
 144	notificationHandlersMu sync.RWMutex
 145	capabilitiesMu         sync.RWMutex
 146	toolFiltersMu          sync.RWMutex
 147
 148	name                   string
 149	version                string
 150	instructions           string
 151	resources              map[string]resourceEntry
 152	resourceTemplates      map[string]resourceTemplateEntry
 153	prompts                map[string]mcp.Prompt
 154	promptHandlers         map[string]PromptHandlerFunc
 155	tools                  map[string]ServerTool
 156	toolHandlerMiddlewares []ToolHandlerMiddleware
 157	toolFilters            []ToolFilterFunc
 158	notificationHandlers   map[string]NotificationHandlerFunc
 159	capabilities           serverCapabilities
 160	paginationLimit        *int
 161	sessions               sync.Map
 162	hooks                  *Hooks
 163}
 164
 165// WithPaginationLimit sets the pagination limit for the server.
 166func WithPaginationLimit(limit int) ServerOption {
 167	return func(s *MCPServer) {
 168		s.paginationLimit = &limit
 169	}
 170}
 171
 172// serverCapabilities defines the supported features of the MCP server
 173type serverCapabilities struct {
 174	tools     *toolCapabilities
 175	resources *resourceCapabilities
 176	prompts   *promptCapabilities
 177	logging   *bool
 178}
 179
 180// resourceCapabilities defines the supported resource-related features
 181type resourceCapabilities struct {
 182	subscribe   bool
 183	listChanged bool
 184}
 185
 186// promptCapabilities defines the supported prompt-related features
 187type promptCapabilities struct {
 188	listChanged bool
 189}
 190
 191// toolCapabilities defines the supported tool-related features
 192type toolCapabilities struct {
 193	listChanged bool
 194}
 195
 196// WithResourceCapabilities configures resource-related server capabilities
 197func WithResourceCapabilities(subscribe, listChanged bool) ServerOption {
 198	return func(s *MCPServer) {
 199		// Always create a non-nil capability object
 200		s.capabilities.resources = &resourceCapabilities{
 201			subscribe:   subscribe,
 202			listChanged: listChanged,
 203		}
 204	}
 205}
 206
 207// WithToolHandlerMiddleware allows adding a middleware for the
 208// tool handler call chain.
 209func WithToolHandlerMiddleware(
 210	toolHandlerMiddleware ToolHandlerMiddleware,
 211) ServerOption {
 212	return func(s *MCPServer) {
 213		s.middlewareMu.Lock()
 214		s.toolHandlerMiddlewares = append(s.toolHandlerMiddlewares, toolHandlerMiddleware)
 215		s.middlewareMu.Unlock()
 216	}
 217}
 218
 219// WithToolFilter adds a filter function that will be applied to tools before they are returned in list_tools
 220func WithToolFilter(
 221	toolFilter ToolFilterFunc,
 222) ServerOption {
 223	return func(s *MCPServer) {
 224		s.toolFiltersMu.Lock()
 225		s.toolFilters = append(s.toolFilters, toolFilter)
 226		s.toolFiltersMu.Unlock()
 227	}
 228}
 229
 230// WithRecovery adds a middleware that recovers from panics in tool handlers.
 231func WithRecovery() ServerOption {
 232	return WithToolHandlerMiddleware(func(next ToolHandlerFunc) ToolHandlerFunc {
 233		return func(ctx context.Context, request mcp.CallToolRequest) (result *mcp.CallToolResult, err error) {
 234			defer func() {
 235				if r := recover(); r != nil {
 236					err = fmt.Errorf(
 237						"panic recovered in %s tool handler: %v",
 238						request.Params.Name,
 239						r,
 240					)
 241				}
 242			}()
 243			return next(ctx, request)
 244		}
 245	})
 246}
 247
 248// WithHooks allows adding hooks that will be called before or after
 249// either [all] requests or before / after specific request methods, or else
 250// prior to returning an error to the client.
 251func WithHooks(hooks *Hooks) ServerOption {
 252	return func(s *MCPServer) {
 253		s.hooks = hooks
 254	}
 255}
 256
 257// WithPromptCapabilities configures prompt-related server capabilities
 258func WithPromptCapabilities(listChanged bool) ServerOption {
 259	return func(s *MCPServer) {
 260		// Always create a non-nil capability object
 261		s.capabilities.prompts = &promptCapabilities{
 262			listChanged: listChanged,
 263		}
 264	}
 265}
 266
 267// WithToolCapabilities configures tool-related server capabilities
 268func WithToolCapabilities(listChanged bool) ServerOption {
 269	return func(s *MCPServer) {
 270		// Always create a non-nil capability object
 271		s.capabilities.tools = &toolCapabilities{
 272			listChanged: listChanged,
 273		}
 274	}
 275}
 276
 277// WithLogging enables logging capabilities for the server
 278func WithLogging() ServerOption {
 279	return func(s *MCPServer) {
 280		s.capabilities.logging = mcp.ToBoolPtr(true)
 281	}
 282}
 283
 284// WithInstructions sets the server instructions for the client returned in the initialize response
 285func WithInstructions(instructions string) ServerOption {
 286	return func(s *MCPServer) {
 287		s.instructions = instructions
 288	}
 289}
 290
 291// NewMCPServer creates a new MCP server instance with the given name, version and options
 292func NewMCPServer(
 293	name, version string,
 294	opts ...ServerOption,
 295) *MCPServer {
 296	s := &MCPServer{
 297		resources:            make(map[string]resourceEntry),
 298		resourceTemplates:    make(map[string]resourceTemplateEntry),
 299		prompts:              make(map[string]mcp.Prompt),
 300		promptHandlers:       make(map[string]PromptHandlerFunc),
 301		tools:                make(map[string]ServerTool),
 302		name:                 name,
 303		version:              version,
 304		notificationHandlers: make(map[string]NotificationHandlerFunc),
 305		capabilities: serverCapabilities{
 306			tools:     nil,
 307			resources: nil,
 308			prompts:   nil,
 309			logging:   nil,
 310		},
 311	}
 312
 313	for _, opt := range opts {
 314		opt(s)
 315	}
 316
 317	return s
 318}
 319
 320// AddResources registers multiple resources at once
 321func (s *MCPServer) AddResources(resources ...ServerResource) {
 322	s.implicitlyRegisterResourceCapabilities()
 323
 324	s.resourcesMu.Lock()
 325	for _, entry := range resources {
 326		s.resources[entry.Resource.URI] = resourceEntry{
 327			resource: entry.Resource,
 328			handler:  entry.Handler,
 329		}
 330	}
 331	s.resourcesMu.Unlock()
 332
 333	// When the list of available resources changes, servers that declared the listChanged capability SHOULD send a notification
 334	if s.capabilities.resources.listChanged {
 335		// Send notification to all initialized sessions
 336		s.SendNotificationToAllClients(mcp.MethodNotificationResourcesListChanged, nil)
 337	}
 338}
 339
 340// AddResource registers a new resource and its handler
 341func (s *MCPServer) AddResource(
 342	resource mcp.Resource,
 343	handler ResourceHandlerFunc,
 344) {
 345	s.AddResources(ServerResource{Resource: resource, Handler: handler})
 346}
 347
 348// RemoveResource removes a resource from the server
 349func (s *MCPServer) RemoveResource(uri string) {
 350	s.resourcesMu.Lock()
 351	_, exists := s.resources[uri]
 352	if exists {
 353		delete(s.resources, uri)
 354	}
 355	s.resourcesMu.Unlock()
 356
 357	// Send notification to all initialized sessions if listChanged capability is enabled and we actually remove a resource
 358	if exists && s.capabilities.resources != nil && s.capabilities.resources.listChanged {
 359		s.SendNotificationToAllClients(mcp.MethodNotificationResourcesListChanged, nil)
 360	}
 361}
 362
 363// AddResourceTemplate registers a new resource template and its handler
 364func (s *MCPServer) AddResourceTemplate(
 365	template mcp.ResourceTemplate,
 366	handler ResourceTemplateHandlerFunc,
 367) {
 368	s.implicitlyRegisterResourceCapabilities()
 369
 370	s.resourcesMu.Lock()
 371	s.resourceTemplates[template.URITemplate.Raw()] = resourceTemplateEntry{
 372		template: template,
 373		handler:  handler,
 374	}
 375	s.resourcesMu.Unlock()
 376
 377	// When the list of available resources changes, servers that declared the listChanged capability SHOULD send a notification
 378	if s.capabilities.resources.listChanged {
 379		// Send notification to all initialized sessions
 380		s.SendNotificationToAllClients(mcp.MethodNotificationResourcesListChanged, nil)
 381	}
 382}
 383
 384// AddPrompts registers multiple prompts at once
 385func (s *MCPServer) AddPrompts(prompts ...ServerPrompt) {
 386	s.implicitlyRegisterPromptCapabilities()
 387
 388	s.promptsMu.Lock()
 389	for _, entry := range prompts {
 390		s.prompts[entry.Prompt.Name] = entry.Prompt
 391		s.promptHandlers[entry.Prompt.Name] = entry.Handler
 392	}
 393	s.promptsMu.Unlock()
 394
 395	// When the list of available prompts changes, servers that declared the listChanged capability SHOULD send a notification.
 396	if s.capabilities.prompts.listChanged {
 397		// Send notification to all initialized sessions
 398		s.SendNotificationToAllClients(mcp.MethodNotificationPromptsListChanged, nil)
 399	}
 400}
 401
 402// AddPrompt registers a new prompt handler with the given name
 403func (s *MCPServer) AddPrompt(prompt mcp.Prompt, handler PromptHandlerFunc) {
 404	s.AddPrompts(ServerPrompt{Prompt: prompt, Handler: handler})
 405}
 406
 407// DeletePrompts removes prompts from the server
 408func (s *MCPServer) DeletePrompts(names ...string) {
 409	s.promptsMu.Lock()
 410	var exists bool
 411	for _, name := range names {
 412		if _, ok := s.prompts[name]; ok {
 413			delete(s.prompts, name)
 414			delete(s.promptHandlers, name)
 415			exists = true
 416		}
 417	}
 418	s.promptsMu.Unlock()
 419
 420	// Send notification to all initialized sessions if listChanged capability is enabled, and we actually remove a prompt
 421	if exists && s.capabilities.prompts != nil && s.capabilities.prompts.listChanged {
 422		// Send notification to all initialized sessions
 423		s.SendNotificationToAllClients(mcp.MethodNotificationPromptsListChanged, nil)
 424	}
 425}
 426
 427// AddTool registers a new tool and its handler
 428func (s *MCPServer) AddTool(tool mcp.Tool, handler ToolHandlerFunc) {
 429	s.AddTools(ServerTool{Tool: tool, Handler: handler})
 430}
 431
 432// Register tool capabilities due to a tool being added.  Default to
 433// listChanged: true, but don't change the value if we've already explicitly
 434// registered tools.listChanged false.
 435func (s *MCPServer) implicitlyRegisterToolCapabilities() {
 436	s.implicitlyRegisterCapabilities(
 437		func() bool { return s.capabilities.tools != nil },
 438		func() { s.capabilities.tools = &toolCapabilities{listChanged: true} },
 439	)
 440}
 441
 442func (s *MCPServer) implicitlyRegisterResourceCapabilities() {
 443	s.implicitlyRegisterCapabilities(
 444		func() bool { return s.capabilities.resources != nil },
 445		func() { s.capabilities.resources = &resourceCapabilities{} },
 446	)
 447}
 448
 449func (s *MCPServer) implicitlyRegisterPromptCapabilities() {
 450	s.implicitlyRegisterCapabilities(
 451		func() bool { return s.capabilities.prompts != nil },
 452		func() { s.capabilities.prompts = &promptCapabilities{} },
 453	)
 454}
 455
 456func (s *MCPServer) implicitlyRegisterCapabilities(check func() bool, register func()) {
 457	s.capabilitiesMu.RLock()
 458	if check() {
 459		s.capabilitiesMu.RUnlock()
 460		return
 461	}
 462	s.capabilitiesMu.RUnlock()
 463
 464	s.capabilitiesMu.Lock()
 465	if !check() {
 466		register()
 467	}
 468	s.capabilitiesMu.Unlock()
 469}
 470
 471// AddTools registers multiple tools at once
 472func (s *MCPServer) AddTools(tools ...ServerTool) {
 473	s.implicitlyRegisterToolCapabilities()
 474
 475	s.toolsMu.Lock()
 476	for _, entry := range tools {
 477		s.tools[entry.Tool.Name] = entry
 478	}
 479	s.toolsMu.Unlock()
 480
 481	// When the list of available tools changes, servers that declared the listChanged capability SHOULD send a notification.
 482	if s.capabilities.tools.listChanged {
 483		// Send notification to all initialized sessions
 484		s.SendNotificationToAllClients(mcp.MethodNotificationToolsListChanged, nil)
 485	}
 486}
 487
 488// SetTools replaces all existing tools with the provided list
 489func (s *MCPServer) SetTools(tools ...ServerTool) {
 490	s.toolsMu.Lock()
 491	s.tools = make(map[string]ServerTool, len(tools))
 492	s.toolsMu.Unlock()
 493	s.AddTools(tools...)
 494}
 495
 496// DeleteTools removes tools from the server
 497func (s *MCPServer) DeleteTools(names ...string) {
 498	s.toolsMu.Lock()
 499	var exists bool
 500	for _, name := range names {
 501		if _, ok := s.tools[name]; ok {
 502			delete(s.tools, name)
 503			exists = true
 504		}
 505	}
 506	s.toolsMu.Unlock()
 507
 508	// When the list of available tools changes, servers that declared the listChanged capability SHOULD send a notification.
 509	if exists && s.capabilities.tools != nil && s.capabilities.tools.listChanged {
 510		// Send notification to all initialized sessions
 511		s.SendNotificationToAllClients(mcp.MethodNotificationToolsListChanged, nil)
 512	}
 513}
 514
 515// AddNotificationHandler registers a new handler for incoming notifications
 516func (s *MCPServer) AddNotificationHandler(
 517	method string,
 518	handler NotificationHandlerFunc,
 519) {
 520	s.notificationHandlersMu.Lock()
 521	defer s.notificationHandlersMu.Unlock()
 522	s.notificationHandlers[method] = handler
 523}
 524
 525func (s *MCPServer) handleInitialize(
 526	ctx context.Context,
 527	_ any,
 528	request mcp.InitializeRequest,
 529) (*mcp.InitializeResult, *requestError) {
 530	capabilities := mcp.ServerCapabilities{}
 531
 532	// Only add resource capabilities if they're configured
 533	if s.capabilities.resources != nil {
 534		capabilities.Resources = &struct {
 535			Subscribe   bool `json:"subscribe,omitempty"`
 536			ListChanged bool `json:"listChanged,omitempty"`
 537		}{
 538			Subscribe:   s.capabilities.resources.subscribe,
 539			ListChanged: s.capabilities.resources.listChanged,
 540		}
 541	}
 542
 543	// Only add prompt capabilities if they're configured
 544	if s.capabilities.prompts != nil {
 545		capabilities.Prompts = &struct {
 546			ListChanged bool `json:"listChanged,omitempty"`
 547		}{
 548			ListChanged: s.capabilities.prompts.listChanged,
 549		}
 550	}
 551
 552	// Only add tool capabilities if they're configured
 553	if s.capabilities.tools != nil {
 554		capabilities.Tools = &struct {
 555			ListChanged bool `json:"listChanged,omitempty"`
 556		}{
 557			ListChanged: s.capabilities.tools.listChanged,
 558		}
 559	}
 560
 561	if s.capabilities.logging != nil && *s.capabilities.logging {
 562		capabilities.Logging = &struct{}{}
 563	}
 564
 565	result := mcp.InitializeResult{
 566		ProtocolVersion: s.protocolVersion(request.Params.ProtocolVersion),
 567		ServerInfo: mcp.Implementation{
 568			Name:    s.name,
 569			Version: s.version,
 570		},
 571		Capabilities: capabilities,
 572		Instructions: s.instructions,
 573	}
 574
 575	if session := ClientSessionFromContext(ctx); session != nil {
 576		session.Initialize()
 577
 578		// Store client info if the session supports it
 579		if sessionWithClientInfo, ok := session.(SessionWithClientInfo); ok {
 580			sessionWithClientInfo.SetClientInfo(request.Params.ClientInfo)
 581		}
 582	}
 583	return &result, nil
 584}
 585
 586func (s *MCPServer) protocolVersion(clientVersion string) string {
 587	if slices.Contains(mcp.ValidProtocolVersions, clientVersion) {
 588		return clientVersion
 589	}
 590
 591	return mcp.LATEST_PROTOCOL_VERSION
 592}
 593
 594func (s *MCPServer) handlePing(
 595	_ context.Context,
 596	_ any,
 597	_ mcp.PingRequest,
 598) (*mcp.EmptyResult, *requestError) {
 599	return &mcp.EmptyResult{}, nil
 600}
 601
 602func (s *MCPServer) handleSetLevel(
 603	ctx context.Context,
 604	id any,
 605	request mcp.SetLevelRequest,
 606) (*mcp.EmptyResult, *requestError) {
 607	clientSession := ClientSessionFromContext(ctx)
 608	if clientSession == nil || !clientSession.Initialized() {
 609		return nil, &requestError{
 610			id:   id,
 611			code: mcp.INTERNAL_ERROR,
 612			err:  ErrSessionNotInitialized,
 613		}
 614	}
 615
 616	sessionLogging, ok := clientSession.(SessionWithLogging)
 617	if !ok {
 618		return nil, &requestError{
 619			id:   id,
 620			code: mcp.INTERNAL_ERROR,
 621			err:  ErrSessionDoesNotSupportLogging,
 622		}
 623	}
 624
 625	level := request.Params.Level
 626	// Validate logging level
 627	switch level {
 628	case mcp.LoggingLevelDebug, mcp.LoggingLevelInfo, mcp.LoggingLevelNotice,
 629		mcp.LoggingLevelWarning, mcp.LoggingLevelError, mcp.LoggingLevelCritical,
 630		mcp.LoggingLevelAlert, mcp.LoggingLevelEmergency:
 631		// Valid level
 632	default:
 633		return nil, &requestError{
 634			id:   id,
 635			code: mcp.INVALID_PARAMS,
 636			err:  fmt.Errorf("invalid logging level '%s'", level),
 637		}
 638	}
 639
 640	sessionLogging.SetLogLevel(level)
 641
 642	return &mcp.EmptyResult{}, nil
 643}
 644
 645func listByPagination[T mcp.Named](
 646	_ context.Context,
 647	s *MCPServer,
 648	cursor mcp.Cursor,
 649	allElements []T,
 650) ([]T, mcp.Cursor, error) {
 651	startPos := 0
 652	if cursor != "" {
 653		c, err := base64.StdEncoding.DecodeString(string(cursor))
 654		if err != nil {
 655			return nil, "", err
 656		}
 657		cString := string(c)
 658		startPos = sort.Search(len(allElements), func(i int) bool {
 659			return allElements[i].GetName() > cString
 660		})
 661	}
 662	endPos := len(allElements)
 663	if s.paginationLimit != nil {
 664		if len(allElements) > startPos+*s.paginationLimit {
 665			endPos = startPos + *s.paginationLimit
 666		}
 667	}
 668	elementsToReturn := allElements[startPos:endPos]
 669	// set the next cursor
 670	nextCursor := func() mcp.Cursor {
 671		if s.paginationLimit != nil && len(elementsToReturn) >= *s.paginationLimit {
 672			nc := elementsToReturn[len(elementsToReturn)-1].GetName()
 673			toString := base64.StdEncoding.EncodeToString([]byte(nc))
 674			return mcp.Cursor(toString)
 675		}
 676		return ""
 677	}()
 678	return elementsToReturn, nextCursor, nil
 679}
 680
 681func (s *MCPServer) handleListResources(
 682	ctx context.Context,
 683	id any,
 684	request mcp.ListResourcesRequest,
 685) (*mcp.ListResourcesResult, *requestError) {
 686	s.resourcesMu.RLock()
 687	resources := make([]mcp.Resource, 0, len(s.resources))
 688	for _, entry := range s.resources {
 689		resources = append(resources, entry.resource)
 690	}
 691	s.resourcesMu.RUnlock()
 692
 693	// Sort the resources by name
 694	sort.Slice(resources, func(i, j int) bool {
 695		return resources[i].Name < resources[j].Name
 696	})
 697	resourcesToReturn, nextCursor, err := listByPagination(
 698		ctx,
 699		s,
 700		request.Params.Cursor,
 701		resources,
 702	)
 703	if err != nil {
 704		return nil, &requestError{
 705			id:   id,
 706			code: mcp.INVALID_PARAMS,
 707			err:  err,
 708		}
 709	}
 710	result := mcp.ListResourcesResult{
 711		Resources: resourcesToReturn,
 712		PaginatedResult: mcp.PaginatedResult{
 713			NextCursor: nextCursor,
 714		},
 715	}
 716	return &result, nil
 717}
 718
 719func (s *MCPServer) handleListResourceTemplates(
 720	ctx context.Context,
 721	id any,
 722	request mcp.ListResourceTemplatesRequest,
 723) (*mcp.ListResourceTemplatesResult, *requestError) {
 724	s.resourcesMu.RLock()
 725	templates := make([]mcp.ResourceTemplate, 0, len(s.resourceTemplates))
 726	for _, entry := range s.resourceTemplates {
 727		templates = append(templates, entry.template)
 728	}
 729	s.resourcesMu.RUnlock()
 730	sort.Slice(templates, func(i, j int) bool {
 731		return templates[i].Name < templates[j].Name
 732	})
 733	templatesToReturn, nextCursor, err := listByPagination(
 734		ctx,
 735		s,
 736		request.Params.Cursor,
 737		templates,
 738	)
 739	if err != nil {
 740		return nil, &requestError{
 741			id:   id,
 742			code: mcp.INVALID_PARAMS,
 743			err:  err,
 744		}
 745	}
 746	result := mcp.ListResourceTemplatesResult{
 747		ResourceTemplates: templatesToReturn,
 748		PaginatedResult: mcp.PaginatedResult{
 749			NextCursor: nextCursor,
 750		},
 751	}
 752	return &result, nil
 753}
 754
 755func (s *MCPServer) handleReadResource(
 756	ctx context.Context,
 757	id any,
 758	request mcp.ReadResourceRequest,
 759) (*mcp.ReadResourceResult, *requestError) {
 760	s.resourcesMu.RLock()
 761	// First try direct resource handlers
 762	if entry, ok := s.resources[request.Params.URI]; ok {
 763		handler := entry.handler
 764		s.resourcesMu.RUnlock()
 765		contents, err := handler(ctx, request)
 766		if err != nil {
 767			return nil, &requestError{
 768				id:   id,
 769				code: mcp.INTERNAL_ERROR,
 770				err:  err,
 771			}
 772		}
 773		return &mcp.ReadResourceResult{Contents: contents}, nil
 774	}
 775
 776	// If no direct handler found, try matching against templates
 777	var matchedHandler ResourceTemplateHandlerFunc
 778	var matched bool
 779	for _, entry := range s.resourceTemplates {
 780		template := entry.template
 781		if matchesTemplate(request.Params.URI, template.URITemplate) {
 782			matchedHandler = entry.handler
 783			matched = true
 784			matchedVars := template.URITemplate.Match(request.Params.URI)
 785			// Convert matched variables to a map
 786			request.Params.Arguments = make(map[string]any, len(matchedVars))
 787			for name, value := range matchedVars {
 788				request.Params.Arguments[name] = value.V
 789			}
 790			break
 791		}
 792	}
 793	s.resourcesMu.RUnlock()
 794
 795	if matched {
 796		contents, err := matchedHandler(ctx, request)
 797		if err != nil {
 798			return nil, &requestError{
 799				id:   id,
 800				code: mcp.INTERNAL_ERROR,
 801				err:  err,
 802			}
 803		}
 804		return &mcp.ReadResourceResult{Contents: contents}, nil
 805	}
 806
 807	return nil, &requestError{
 808		id:   id,
 809		code: mcp.RESOURCE_NOT_FOUND,
 810		err: fmt.Errorf(
 811			"handler not found for resource URI '%s': %w",
 812			request.Params.URI,
 813			ErrResourceNotFound,
 814		),
 815	}
 816}
 817
 818// matchesTemplate checks if a URI matches a URI template pattern
 819func matchesTemplate(uri string, template *mcp.URITemplate) bool {
 820	return template.Regexp().MatchString(uri)
 821}
 822
 823func (s *MCPServer) handleListPrompts(
 824	ctx context.Context,
 825	id any,
 826	request mcp.ListPromptsRequest,
 827) (*mcp.ListPromptsResult, *requestError) {
 828	s.promptsMu.RLock()
 829	prompts := make([]mcp.Prompt, 0, len(s.prompts))
 830	for _, prompt := range s.prompts {
 831		prompts = append(prompts, prompt)
 832	}
 833	s.promptsMu.RUnlock()
 834
 835	// sort prompts by name
 836	sort.Slice(prompts, func(i, j int) bool {
 837		return prompts[i].Name < prompts[j].Name
 838	})
 839	promptsToReturn, nextCursor, err := listByPagination(
 840		ctx,
 841		s,
 842		request.Params.Cursor,
 843		prompts,
 844	)
 845	if err != nil {
 846		return nil, &requestError{
 847			id:   id,
 848			code: mcp.INVALID_PARAMS,
 849			err:  err,
 850		}
 851	}
 852	result := mcp.ListPromptsResult{
 853		Prompts: promptsToReturn,
 854		PaginatedResult: mcp.PaginatedResult{
 855			NextCursor: nextCursor,
 856		},
 857	}
 858	return &result, nil
 859}
 860
 861func (s *MCPServer) handleGetPrompt(
 862	ctx context.Context,
 863	id any,
 864	request mcp.GetPromptRequest,
 865) (*mcp.GetPromptResult, *requestError) {
 866	s.promptsMu.RLock()
 867	handler, ok := s.promptHandlers[request.Params.Name]
 868	s.promptsMu.RUnlock()
 869
 870	if !ok {
 871		return nil, &requestError{
 872			id:   id,
 873			code: mcp.INVALID_PARAMS,
 874			err:  fmt.Errorf("prompt '%s' not found: %w", request.Params.Name, ErrPromptNotFound),
 875		}
 876	}
 877
 878	result, err := handler(ctx, request)
 879	if err != nil {
 880		return nil, &requestError{
 881			id:   id,
 882			code: mcp.INTERNAL_ERROR,
 883			err:  err,
 884		}
 885	}
 886
 887	return result, nil
 888}
 889
 890func (s *MCPServer) handleListTools(
 891	ctx context.Context,
 892	id any,
 893	request mcp.ListToolsRequest,
 894) (*mcp.ListToolsResult, *requestError) {
 895	// Get the base tools from the server
 896	s.toolsMu.RLock()
 897	tools := make([]mcp.Tool, 0, len(s.tools))
 898
 899	// Get all tool names for consistent ordering
 900	toolNames := make([]string, 0, len(s.tools))
 901	for name := range s.tools {
 902		toolNames = append(toolNames, name)
 903	}
 904
 905	// Sort the tool names for consistent ordering
 906	sort.Strings(toolNames)
 907
 908	// Add tools in sorted order
 909	for _, name := range toolNames {
 910		tools = append(tools, s.tools[name].Tool)
 911	}
 912	s.toolsMu.RUnlock()
 913
 914	// Check if there are session-specific tools
 915	session := ClientSessionFromContext(ctx)
 916	if session != nil {
 917		if sessionWithTools, ok := session.(SessionWithTools); ok {
 918			if sessionTools := sessionWithTools.GetSessionTools(); sessionTools != nil {
 919				// Override or add session-specific tools
 920				// We need to create a map first to merge the tools properly
 921				toolMap := make(map[string]mcp.Tool)
 922
 923				// Add global tools first
 924				for _, tool := range tools {
 925					toolMap[tool.Name] = tool
 926				}
 927
 928				// Then override with session-specific tools
 929				for name, serverTool := range sessionTools {
 930					toolMap[name] = serverTool.Tool
 931				}
 932
 933				// Convert back to slice
 934				tools = make([]mcp.Tool, 0, len(toolMap))
 935				for _, tool := range toolMap {
 936					tools = append(tools, tool)
 937				}
 938
 939				// Sort again to maintain consistent ordering
 940				sort.Slice(tools, func(i, j int) bool {
 941					return tools[i].Name < tools[j].Name
 942				})
 943			}
 944		}
 945	}
 946
 947	// Apply tool filters if any are defined
 948	s.toolFiltersMu.RLock()
 949	if len(s.toolFilters) > 0 {
 950		for _, filter := range s.toolFilters {
 951			tools = filter(ctx, tools)
 952		}
 953	}
 954	s.toolFiltersMu.RUnlock()
 955
 956	// Apply pagination
 957	toolsToReturn, nextCursor, err := listByPagination(
 958		ctx,
 959		s,
 960		request.Params.Cursor,
 961		tools,
 962	)
 963	if err != nil {
 964		return nil, &requestError{
 965			id:   id,
 966			code: mcp.INVALID_PARAMS,
 967			err:  err,
 968		}
 969	}
 970
 971	result := mcp.ListToolsResult{
 972		Tools: toolsToReturn,
 973		PaginatedResult: mcp.PaginatedResult{
 974			NextCursor: nextCursor,
 975		},
 976	}
 977	return &result, nil
 978}
 979
 980func (s *MCPServer) handleToolCall(
 981	ctx context.Context,
 982	id any,
 983	request mcp.CallToolRequest,
 984) (*mcp.CallToolResult, *requestError) {
 985	// First check session-specific tools
 986	var tool ServerTool
 987	var ok bool
 988
 989	session := ClientSessionFromContext(ctx)
 990	if session != nil {
 991		if sessionWithTools, typeAssertOk := session.(SessionWithTools); typeAssertOk {
 992			if sessionTools := sessionWithTools.GetSessionTools(); sessionTools != nil {
 993				var sessionOk bool
 994				tool, sessionOk = sessionTools[request.Params.Name]
 995				if sessionOk {
 996					ok = true
 997				}
 998			}
 999		}
1000	}
1001
1002	// If not found in session tools, check global tools
1003	if !ok {
1004		s.toolsMu.RLock()
1005		tool, ok = s.tools[request.Params.Name]
1006		s.toolsMu.RUnlock()
1007	}
1008
1009	if !ok {
1010		return nil, &requestError{
1011			id:   id,
1012			code: mcp.INVALID_PARAMS,
1013			err:  fmt.Errorf("tool '%s' not found: %w", request.Params.Name, ErrToolNotFound),
1014		}
1015	}
1016
1017	finalHandler := tool.Handler
1018
1019	s.middlewareMu.RLock()
1020	mw := s.toolHandlerMiddlewares
1021	s.middlewareMu.RUnlock()
1022
1023	// Apply middlewares in reverse order
1024	for i := len(mw) - 1; i >= 0; i-- {
1025		finalHandler = mw[i](finalHandler)
1026	}
1027
1028	result, err := finalHandler(ctx, request)
1029	if err != nil {
1030		return nil, &requestError{
1031			id:   id,
1032			code: mcp.INTERNAL_ERROR,
1033			err:  err,
1034		}
1035	}
1036
1037	return result, nil
1038}
1039
1040func (s *MCPServer) handleNotification(
1041	ctx context.Context,
1042	notification mcp.JSONRPCNotification,
1043) mcp.JSONRPCMessage {
1044	s.notificationHandlersMu.RLock()
1045	handler, ok := s.notificationHandlers[notification.Method]
1046	s.notificationHandlersMu.RUnlock()
1047
1048	if ok {
1049		handler(ctx, notification)
1050	}
1051	return nil
1052}
1053
1054func createResponse(id any, result any) mcp.JSONRPCMessage {
1055	return mcp.JSONRPCResponse{
1056		JSONRPC: mcp.JSONRPC_VERSION,
1057		ID:      mcp.NewRequestId(id),
1058		Result:  result,
1059	}
1060}
1061
1062func createErrorResponse(
1063	id any,
1064	code int,
1065	message string,
1066) mcp.JSONRPCMessage {
1067	return mcp.JSONRPCError{
1068		JSONRPC: mcp.JSONRPC_VERSION,
1069		ID:      mcp.NewRequestId(id),
1070		Error: struct {
1071			Code    int    `json:"code"`
1072			Message string `json:"message"`
1073			Data    any    `json:"data,omitempty"`
1074		}{
1075			Code:    code,
1076			Message: message,
1077		},
1078	}
1079}