diff --git a/pkg/mcp/mcp.go b/pkg/mcp/mcp.go index 319f698..d0ce828 100644 --- a/pkg/mcp/mcp.go +++ b/pkg/mcp/mcp.go @@ -6,6 +6,7 @@ package mcp import ( "context" + "errors" "iter" "net/http" "os" @@ -506,6 +507,10 @@ type EditDiffOutput struct { // Tool handlers func (s *Service) readFile(ctx context.Context, req *mcp.CallToolRequest, input ReadFileInput) (*mcp.CallToolResult, ReadFileOutput, error) { + if s.medium == nil { + return nil, ReadFileOutput{}, log.E("mcp.readFile", "workspace medium unavailable", nil) + } + content, err := s.medium.Read(input.Path) if err != nil { return nil, ReadFileOutput{}, log.E("mcp.readFile", "failed to read file", err) @@ -518,6 +523,10 @@ func (s *Service) readFile(ctx context.Context, req *mcp.CallToolRequest, input } func (s *Service) writeFile(ctx context.Context, req *mcp.CallToolRequest, input WriteFileInput) (*mcp.CallToolResult, WriteFileOutput, error) { + if s.medium == nil { + return nil, WriteFileOutput{}, log.E("mcp.writeFile", "workspace medium unavailable", nil) + } + // Medium.Write creates parent directories automatically if err := s.medium.Write(input.Path, input.Content); err != nil { return nil, WriteFileOutput{}, log.E("mcp.writeFile", "failed to write file", err) @@ -526,6 +535,10 @@ func (s *Service) writeFile(ctx context.Context, req *mcp.CallToolRequest, input } func (s *Service) listDirectory(ctx context.Context, req *mcp.CallToolRequest, input ListDirectoryInput) (*mcp.CallToolResult, ListDirectoryOutput, error) { + if s.medium == nil { + return nil, ListDirectoryOutput{}, log.E("mcp.listDirectory", "workspace medium unavailable", nil) + } + entries, err := s.medium.List(input.Path) if err != nil { return nil, ListDirectoryOutput{}, log.E("mcp.listDirectory", "failed to list directory", err) @@ -563,6 +576,10 @@ func directoryEntryPath(dir, name string) string { } func (s *Service) createDirectory(ctx context.Context, req *mcp.CallToolRequest, input CreateDirectoryInput) (*mcp.CallToolResult, CreateDirectoryOutput, error) { + if s.medium == nil { + return nil, CreateDirectoryOutput{}, log.E("mcp.createDirectory", "workspace medium unavailable", nil) + } + if err := s.medium.EnsureDir(input.Path); err != nil { return nil, CreateDirectoryOutput{}, log.E("mcp.createDirectory", "failed to create directory", err) } @@ -570,6 +587,10 @@ func (s *Service) createDirectory(ctx context.Context, req *mcp.CallToolRequest, } func (s *Service) deleteFile(ctx context.Context, req *mcp.CallToolRequest, input DeleteFileInput) (*mcp.CallToolResult, DeleteFileOutput, error) { + if s.medium == nil { + return nil, DeleteFileOutput{}, log.E("mcp.deleteFile", "workspace medium unavailable", nil) + } + if err := s.medium.Delete(input.Path); err != nil { return nil, DeleteFileOutput{}, log.E("mcp.deleteFile", "failed to delete file", err) } @@ -577,6 +598,10 @@ func (s *Service) deleteFile(ctx context.Context, req *mcp.CallToolRequest, inpu } func (s *Service) renameFile(ctx context.Context, req *mcp.CallToolRequest, input RenameFileInput) (*mcp.CallToolResult, RenameFileOutput, error) { + if s.medium == nil { + return nil, RenameFileOutput{}, log.E("mcp.renameFile", "workspace medium unavailable", nil) + } + if err := s.medium.Rename(input.OldPath, input.NewPath); err != nil { return nil, RenameFileOutput{}, log.E("mcp.renameFile", "failed to rename file", err) } @@ -584,9 +609,16 @@ func (s *Service) renameFile(ctx context.Context, req *mcp.CallToolRequest, inpu } func (s *Service) fileExists(ctx context.Context, req *mcp.CallToolRequest, input FileExistsInput) (*mcp.CallToolResult, FileExistsOutput, error) { + if s.medium == nil { + return nil, FileExistsOutput{}, log.E("mcp.fileExists", "workspace medium unavailable", nil) + } + info, err := s.medium.Stat(input.Path) if err != nil { - return nil, FileExistsOutput{Exists: false, IsDir: false, Path: input.Path}, nil + if errors.Is(err, os.ErrNotExist) { + return nil, FileExistsOutput{Exists: false, IsDir: false, Path: input.Path}, nil + } + return nil, FileExistsOutput{}, log.E("mcp.fileExists", "failed to stat path", err) } return nil, FileExistsOutput{ Exists: true, @@ -605,6 +637,10 @@ func (s *Service) getSupportedLanguages(ctx context.Context, req *mcp.CallToolRe } func (s *Service) editDiff(ctx context.Context, req *mcp.CallToolRequest, input EditDiffInput) (*mcp.CallToolResult, EditDiffOutput, error) { + if s.medium == nil { + return nil, EditDiffOutput{}, log.E("mcp.editDiff", "workspace medium unavailable", nil) + } + if input.OldString == "" { return nil, EditDiffOutput{}, log.E("mcp.editDiff", "old_string cannot be empty", nil) } diff --git a/pkg/mcp/tools_process.go b/pkg/mcp/tools_process.go index dd064fc..bf0455e 100644 --- a/pkg/mcp/tools_process.go +++ b/pkg/mcp/tools_process.go @@ -176,6 +176,10 @@ func (s *Service) registerProcessTools(server *mcp.Server) bool { // processStart handles the process_start tool call. func (s *Service) processStart(ctx context.Context, req *mcp.CallToolRequest, input ProcessStartInput) (*mcp.CallToolResult, ProcessStartOutput, error) { + if s.processService == nil { + return nil, ProcessStartOutput{}, log.E("processStart", "process service unavailable", nil) + } + s.logger.Security("MCP tool execution", "tool", "process_start", "command", input.Command, "args", input.Args, "dir", input.Dir, "user", log.Username()) if input.Command == "" { @@ -222,6 +226,10 @@ func (s *Service) processStart(ctx context.Context, req *mcp.CallToolRequest, in // processStop handles the process_stop tool call. func (s *Service) processStop(ctx context.Context, req *mcp.CallToolRequest, input ProcessStopInput) (*mcp.CallToolResult, ProcessStopOutput, error) { + if s.processService == nil { + return nil, ProcessStopOutput{}, log.E("processStop", "process service unavailable", nil) + } + s.logger.Security("MCP tool execution", "tool", "process_stop", "id", input.ID, "user", log.Username()) if input.ID == "" { @@ -260,6 +268,10 @@ func (s *Service) processStop(ctx context.Context, req *mcp.CallToolRequest, inp // processKill handles the process_kill tool call. func (s *Service) processKill(ctx context.Context, req *mcp.CallToolRequest, input ProcessKillInput) (*mcp.CallToolResult, ProcessKillOutput, error) { + if s.processService == nil { + return nil, ProcessKillOutput{}, log.E("processKill", "process service unavailable", nil) + } + s.logger.Security("MCP tool execution", "tool", "process_kill", "id", input.ID, "user", log.Username()) if input.ID == "" { @@ -296,6 +308,10 @@ func (s *Service) processKill(ctx context.Context, req *mcp.CallToolRequest, inp // processList handles the process_list tool call. func (s *Service) processList(ctx context.Context, req *mcp.CallToolRequest, input ProcessListInput) (*mcp.CallToolResult, ProcessListOutput, error) { + if s.processService == nil { + return nil, ProcessListOutput{}, log.E("processList", "process service unavailable", nil) + } + s.logger.Info("MCP tool execution", "tool", "process_list", "running_only", input.RunningOnly, "user", log.Username()) var procs []*process.Process @@ -329,6 +345,10 @@ func (s *Service) processList(ctx context.Context, req *mcp.CallToolRequest, inp // processOutput handles the process_output tool call. func (s *Service) processOutput(ctx context.Context, req *mcp.CallToolRequest, input ProcessOutputInput) (*mcp.CallToolResult, ProcessOutputOutput, error) { + if s.processService == nil { + return nil, ProcessOutputOutput{}, log.E("processOutput", "process service unavailable", nil) + } + s.logger.Info("MCP tool execution", "tool", "process_output", "id", input.ID, "user", log.Username()) if input.ID == "" { @@ -349,6 +369,10 @@ func (s *Service) processOutput(ctx context.Context, req *mcp.CallToolRequest, i // processInput handles the process_input tool call. func (s *Service) processInput(ctx context.Context, req *mcp.CallToolRequest, input ProcessInputInput) (*mcp.CallToolResult, ProcessInputOutput, error) { + if s.processService == nil { + return nil, ProcessInputOutput{}, log.E("processInput", "process service unavailable", nil) + } + s.logger.Security("MCP tool execution", "tool", "process_input", "id", input.ID, "user", log.Username()) if input.ID == "" { diff --git a/pkg/mcp/tools_ws.go b/pkg/mcp/tools_ws.go index 4f24181..d76717a 100644 --- a/pkg/mcp/tools_ws.go +++ b/pkg/mcp/tools_ws.go @@ -64,6 +64,10 @@ func (s *Service) registerWSTools(server *mcp.Server) bool { // wsStart handles the ws_start tool call. func (s *Service) wsStart(ctx context.Context, req *mcp.CallToolRequest, input WSStartInput) (*mcp.CallToolResult, WSStartOutput, error) { + if s.wsHub == nil { + return nil, WSStartOutput{}, log.E("wsStart", "websocket hub unavailable", nil) + } + addr := input.Addr if addr == "" { addr = ":8080" @@ -119,6 +123,10 @@ func (s *Service) wsStart(ctx context.Context, req *mcp.CallToolRequest, input W // wsInfo handles the ws_info tool call. func (s *Service) wsInfo(ctx context.Context, req *mcp.CallToolRequest, input WSInfoInput) (*mcp.CallToolResult, WSInfoOutput, error) { + if s.wsHub == nil { + return nil, WSInfoOutput{}, log.E("wsInfo", "websocket hub unavailable", nil) + } + s.logger.Info("MCP tool execution", "tool", "ws_info", "user", log.Username()) stats := s.wsHub.Stats()