88 "fmt"
99 "io"
1010 "log"
11- "math/rand"
1211 "os"
1312 "os/signal"
1413 "strings"
@@ -18,6 +17,7 @@ import (
1817 "github.com/FreePeak/cortex/internal/domain"
1918 "github.com/FreePeak/cortex/internal/infrastructure/logging"
2019 "github.com/FreePeak/cortex/internal/interfaces/rest"
20+ "github.com/google/uuid"
2121)
2222
2323// Constants for JSON-RPC
@@ -70,11 +70,12 @@ func WithStdioContextFunc(fn StdioContextFunc) StdioOption {
7070// It will create a custom logger that wraps the standard log.Logger
7171func WithErrorLogger (stdLogger * log.Logger ) StdioOption {
7272 return func (s * StdioServer ) {
73- // Create a development logger that outputs to stderr
73+ // Create a development logger that always outputs to stderr
74+ // In STDIO mode, stdout is reserved for JSON-RPC messages only
7475 logger , err := logging .New (logging.Config {
7576 Level : logging .InfoLevel ,
7677 Development : true ,
77- OutputPaths : []string {"stderr" },
78+ OutputPaths : []string {"stderr" }, // Force stderr for all logging
7879 })
7980 if err != nil {
8081 // If we can't create the logger, use the default one
@@ -87,11 +88,11 @@ func WithErrorLogger(stdLogger *log.Logger) StdioOption {
8788// NewStdioServer creates a new stdio server wrapper around an MCPServer.
8889// It initializes the server with a default logger that logs to stderr.
8990func NewStdioServer (server * rest.MCPServer , opts ... StdioOption ) * StdioServer {
90- // Create default logger
91+ // Create default logger - always use stderr for STDIO servers
9192 defaultLogger , err := logging .New (logging.Config {
9293 Level : logging .InfoLevel ,
9394 Development : true ,
94- OutputPaths : []string {"stderr" },
95+ OutputPaths : []string {"stderr" }, // Force stderr for all logging output
9596 InitialFields : logging.Fields {
9697 "component" : "stdio-server" ,
9798 },
@@ -453,160 +454,51 @@ func (p *MessageProcessor) handleToolsCall(ctx context.Context, params interface
453454 }
454455 }
455456
456- // Get available tools from the service
457- tools , err := p .server .GetService ().ListTools (ctx )
458- if err != nil {
459- return nil , & domain.JSONRPCError {
460- Code : InternalErrorCode ,
461- Message : fmt .Sprintf ("Internal error: %v" , err ),
462- }
457+ // Create a client session for the tool handler
458+ clientSession := & domain.ClientSession {
459+ ID : generateSessionID (),
460+ Connected : true ,
463461 }
464462
465- // Find the requested tool
466- var foundTool * domain.Tool
467- var toolFound bool
468-
469- for _ , tool := range tools {
470- if tool .Name == toolName {
471- foundTool = tool
472- toolFound = true
473- break
474- }
475- }
476-
477- if ! toolFound {
478- return nil , & domain.JSONRPCError {
479- Code : MethodNotFoundCode ,
480- Message : fmt .Sprintf ("Tool not found: %s" , toolName ),
481- }
482- }
463+ // Access the service to get the tool handler
464+ service := p .server .GetService ()
483465
484- // Validate required parameters
485- missingParams := []string {}
486- for _ , param := range foundTool .Parameters {
487- if param .Required {
488- paramValue , exists := toolParams [param .Name ]
489- if ! exists || paramValue == nil {
490- missingParams = append (missingParams , param .Name )
466+ // Try to get a registered handler for this tool
467+ handler := service .GetToolHandler (toolName )
468+ if handler != nil {
469+ // We have a registered handler, use it
470+ p .logger .Info ("Using registered handler for tool" , logging.Fields {"tool" : toolName })
471+ result , err := handler (ctx , toolParams , clientSession )
472+ if err != nil {
473+ p .logger .Error ("Error executing tool handler" , logging.Fields {"tool" : toolName , "error" : err })
474+ return nil , & domain.JSONRPCError {
475+ Code : InternalErrorCode ,
476+ Message : fmt .Sprintf ("Error executing tool: %v" , err ),
491477 }
492478 }
493- }
494-
495- if len (missingParams ) > 0 {
496- return nil , & domain.JSONRPCError {
497- Code : InvalidParamsCode ,
498- Message : fmt .Sprintf ("Missing required parameters: %s" , strings .Join (missingParams , ", " )),
499- }
500- }
501-
502- // Handle different tool types using a strategy pattern
503- var toolResult interface {}
504- var toolErr error
505-
506- // Use exact tool name matching instead of prefix matching
507- switch toolName {
508- case "mcp_golang_mcp_server_stdio_echo" , "mcp_golang_mcp_server_stdio_cortex_echo" :
509- toolResult , toolErr = handleEchoTool (toolParams )
510- case "mcp_golang_mcp_server_stdio_weather" , "mcp_golang_mcp_server_stdio_cortex_weather" :
511- toolResult , toolErr = handleWeatherTool (toolParams )
512- default :
513- return nil , & domain.JSONRPCError {
514- Code : InternalErrorCode ,
515- Message : fmt .Sprintf ("Tool '%s' is registered but has no implementation" , toolName ),
516- }
517- }
518479
519- if toolErr != nil {
520- return nil , & domain.JSONRPCError {
521- Code : InternalErrorCode ,
522- Message : toolErr .Error (),
523- }
480+ p .logger .Info ("Tool executed successfully" , logging.Fields {"tool" : toolName })
481+ return result , nil
524482 }
525483
526- return toolResult , nil
527- }
484+ // No handler found - log available handlers and return error
485+ availableHandlers := service .GetAllToolHandlerNames ()
486+ p .logger .Warn ("No registered handler found for tool" , logging.Fields {
487+ "tool" : toolName ,
488+ "availableHandlers" : fmt .Sprintf ("%+v" , availableHandlers ),
489+ })
528490
529- // Handle echo tool types
530- func handleEchoTool (params map [string ]interface {}) (interface {}, error ) {
531- var message string
532-
533- // Extract message parameter
534- messageVal , exists := params ["message" ]
535- if ! exists || messageVal == nil {
536- return nil , fmt .Errorf ("missing 'message' parameter" )
537- }
538-
539- // Convert to string based on type
540- switch v := messageVal .(type ) {
541- case string :
542- message = v
543- case float64 , int , int64 , float32 :
544- message = fmt .Sprintf ("%v" , v )
545- default :
546- // Try JSON conversion for complex types
547- jsonBytes , err := json .Marshal (v )
548- if err != nil {
549- message = fmt .Sprintf ("%v" , v )
550- } else {
551- message = string (jsonBytes )
552- }
491+ return nil , & domain.JSONRPCError {
492+ Code : InternalErrorCode ,
493+ Message : fmt .Sprintf ("Tool '%s' is registered but has no implementation" , toolName ),
553494 }
554-
555- // Return formatted result using the MCP content format
556- return map [string ]interface {}{
557- "content" : []map [string ]interface {}{
558- {
559- "type" : "text" ,
560- "text" : message ,
561- },
562- },
563- }, nil
564495}
565496
566- // Handle weather tool types
567- func handleWeatherTool (params map [string ]interface {}) (interface {}, error ) {
568- // Extract location parameter
569- locationVal , exists := params ["location" ]
570- if ! exists || locationVal == nil {
571- return nil , fmt .Errorf ("missing 'location' parameter" )
572- }
573-
574- location , ok := locationVal .(string )
575- if ! ok {
576- return nil , fmt .Errorf ("location must be a string" )
577- }
578-
579- // Generate random weather data for testing
580- rand .Seed (time .Now ().UnixNano ())
581- conditions := []string {"Sunny" , "Partly Cloudy" , "Cloudy" , "Rainy" , "Thunderstorms" , "Snowy" , "Foggy" , "Windy" }
582- tempF := rand .Intn (50 ) + 30 // Random temperature between 30°F and 80°F
583- tempC := (tempF - 32 ) * 5 / 9
584- humidity := rand .Intn (60 ) + 30 // Random humidity between 30% and 90%
585- windSpeed := rand .Intn (20 ) + 5 // Random wind speed between 5-25mph
586-
587- // Select a random condition
588- condition := conditions [rand .Intn (len (conditions ))]
589-
590- // Format today's date
591- today := time .Now ().Format ("Monday, January 2, 2006" )
592-
593- // Format the weather response
594- weatherInfo := fmt .Sprintf ("Weather for %s on %s:\n Condition: %s\n Temperature: %d°F (%d°C)\n Humidity: %d%%\n Wind Speed: %d mph" ,
595- location , today , condition , tempF , tempC , humidity , windSpeed )
596-
597- // Return the weather response in the format expected by the MCP protocol
598- return map [string ]interface {}{
599- "content" : []map [string ]interface {}{
600- {
601- "type" : "text" ,
602- "text" : weatherInfo ,
603- },
604- },
605- }, nil
497+ // generateSessionID creates a unique session ID
498+ func generateSessionID () string {
499+ return uuid .New ().String ()
606500}
607501
608- // Helper functions for error handling and response creation
609-
610502// isTerminalError determines if an error should cause the server to shut down
611503func isTerminalError (err error ) bool {
612504 if err == nil {
0 commit comments