From 86fa7026420233a8c391c9c90d66bcfcffd2a3d3 Mon Sep 17 00:00:00 2001 From: "elvandlie@gmail.com" Date: Thu, 21 May 2026 03:01:04 +0700 Subject: [PATCH] fix(mcp): add readonly guard to createHandler The createHandler lacks the s.readonly.Load() check that is present in other mutating tools like deployHandler and deleteHandler. Because of this, an MCP client can execute the create command (which writes project scaffolding files to the disk) even when the server is operating in readonly mode. Added the guard to match the behavior of other tools. Fixes #3810 --- pkg/mcp/tools_create.go | 4 ++++ pkg/mcp/tools_create_test.go | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/pkg/mcp/tools_create.go b/pkg/mcp/tools_create.go index 28e4b7a03e..97b4054ee9 100644 --- a/pkg/mcp/tools_create.go +++ b/pkg/mcp/tools_create.go @@ -20,6 +20,10 @@ var createTool = &mcp.Tool{ } func (s *Server) createHandler(ctx context.Context, r *mcp.CallToolRequest, input CreateInput) (result *mcp.CallToolResult, output CreateOutput, err error) { + if s.readonly.Load() { + err = fmt.Errorf("the server is currently in readonly mode. Please set FUNC_ENABLE_MCP_WRITE and restart the client") + return + } out, err := s.executor.Execute(ctx, "create", input.Args()...) if err != nil { err = fmt.Errorf("%w\n%s", err, string(out)) diff --git a/pkg/mcp/tools_create_test.go b/pkg/mcp/tools_create_test.go index 737102b8a3..25460776d1 100644 --- a/pkg/mcp/tools_create_test.go +++ b/pkg/mcp/tools_create_test.go @@ -123,3 +123,25 @@ func TestTool_Create_BinaryFailure(t *testing.T) { t.Errorf("expected error to include binary output, got: %s", resultToString(result)) } } + +// TestTool_Create_Readonly ensures the create tool rejects requests in readonly mode. +func TestTool_Create_Readonly(t *testing.T) { + client, _, err := newTestPairWithReadonly(t, true) // readonly = true + if err != nil { + t.Fatal(err) + } + + result, err := client.CallTool(t.Context(), &mcp.CallToolParams{ + Name: "create", + Arguments: map[string]any{"language": "go", "path": "."}, + }) + if err != nil { + t.Fatal(err) + } + if !result.IsError { + t.Fatal("expected create to be rejected in readonly mode") + } + if !strings.Contains(resultToString(result), "readonly mode") { + t.Errorf("expected readonly error message, got: %s", resultToString(result)) + } +}