fix(mcp): harden tool handlers

This commit is contained in:
Virgil 2026-04-02 18:50:20 +00:00
parent ab1aa0cad0
commit 0b63f9cd22
3 changed files with 69 additions and 1 deletions

View file

@ -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,10 +609,17 @@ 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 {
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,
IsDir: info.IsDir(),
@ -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)
}

View file

@ -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 == "" {

View file

@ -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()