Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions pkg/mcp/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"os/exec"
"strings"
"sync/atomic"

"github.com/modelcontextprotocol/go-sdk/mcp"
)
Expand All @@ -22,7 +23,7 @@ const (
type Server struct {
OnInit func(context.Context) // Invoked when the server is initialized
prefix string // Command prefix ("func" or "kn func")
readonly bool // disables deploy and delete when true
readonly atomic.Bool // disables deploy and delete when true
executor executor
transport mcp.Transport // Transport to use (defaults to StdioTransport)
impl *mcp.Server // implements the protocol
Expand Down Expand Up @@ -58,7 +59,7 @@ func WithTransport(transport mcp.Transport) Option {
// WithReadonly sets the server to readonly mode.
func WithReadonly(readonly bool) Option {
return func(s *Server) {
s.readonly = readonly
s.readonly.Store(readonly)
}
}

Expand All @@ -80,7 +81,7 @@ func New(options ...Option) *Server {
Title: title,
Version: version},
&mcp.ServerOptions{
Instructions: instructions(s.readonly),
Instructions: instructions(s.readonly.Load()),
HasPrompts: true,
HasResources: true,
HasTools: true,
Expand Down Expand Up @@ -146,7 +147,7 @@ func New(options ...Option) *Server {

// Start the MCP server using the configured transport
func (s *Server) Start(ctx context.Context, writeEnabled bool) error {
s.readonly = !writeEnabled
s.readonly.Store(!writeEnabled)
return s.impl.Run(ctx, s.transport)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/mcp/tools_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ var deleteTool = &mcp.Tool{
}

func (s *Server) deleteHandler(ctx context.Context, r *mcp.CallToolRequest, input DeleteInput) (result *mcp.CallToolResult, output DeleteOutput, err error) {
if s.readonly {
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
}
Expand Down
21 changes: 20 additions & 1 deletion pkg/mcp/tools_delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func TestTool_Delete_Args(t *testing.T) {
t.Fatal(err)
}
// Delete requires write mode - enable it for this test
server.readonly = false
server.readonly.Store(false)

// Build input arguments from test data
inputArgs := buildInputArgs(stringFlags, boolFlags)
Expand All @@ -76,3 +76,22 @@ func TestTool_Delete_Args(t *testing.T) {
t.Fatal("executor was not invoked")
}
}

// TestTool_Delete_Readonly ensures the delete tool rejects requests in readonly mode.
func TestTool_Delete_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: "delete",
Arguments: map[string]any{"name": "my-function"},
})
if err != nil {
t.Fatal(err)
}
if !result.IsError {
t.Fatal("expected delete to be rejected in readonly mode")
}
}
2 changes: 1 addition & 1 deletion pkg/mcp/tools_deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ var deployTool = &mcp.Tool{
}

func (s *Server) deployHandler(ctx context.Context, r *mcp.CallToolRequest, input DeployInput) (result *mcp.CallToolResult, output DeployOutput, err error) {
if s.readonly {
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
}
Expand Down
21 changes: 20 additions & 1 deletion pkg/mcp/tools_deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func TestTool_Deploy_Args(t *testing.T) {
t.Fatal(err)
}
// Deploy requires write mode - enable it for this test
server.readonly = false
server.readonly.Store(false)

// Build input arguments from test data
inputArgs := buildInputArgs(stringFlags, boolFlags)
Expand All @@ -79,3 +79,22 @@ func TestTool_Deploy_Args(t *testing.T) {
t.Fatal("executor was not invoked")
}
}

// TestTool_Deploy_Readonly ensures the deploy tool rejects requests in readonly mode.
func TestTool_Deploy_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: "deploy",
Arguments: map[string]any{"path": "."},
})
if err != nil {
t.Fatal(err)
}
if !result.IsError {
t.Fatal("expected deploy to be rejected in readonly mode")
}
}
Loading