This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 25f1bcd04 fix(rmqctl): resolve explicit config paths in MCP snippets 
(#6460)
25f1bcd04 is described below

commit 25f1bcd045bed38a0608078e392b674814a73270
Author: Loyal-Young <[email protected]>
AuthorDate: Thu Oct 15 20:28:30 2026 +0800

    fix(rmqctl): resolve explicit config paths in MCP snippets (#6460)
    
    * fix(rmqctl): resolve explicit config paths in MCP snippets
    
    * style(rmqctl): complete the ASF header and group stdlib imports
    
    Expand the abbreviated license header in mcp_config_path_test.go to the full
    text used by every other file in the package, and move "path/filepath" into 
a
    stdlib import group ahead of the third-party ones, matching cmd/version.go.
    
    ---------
    
    Co-authored-by: lizhimins <[email protected]>
---
 rmqctl/cmd/mcp.go                    |  9 ++++-
 rmqctl/cmd/mcp_config_golden_test.go | 17 ++++++++-
 rmqctl/cmd/mcp_config_path_test.go   | 73 ++++++++++++++++++++++++++++++++++++
 rmqctl/cmd/mcp_stdio_test.go         |  9 ++++-
 4 files changed, 104 insertions(+), 4 deletions(-)

diff --git a/rmqctl/cmd/mcp.go b/rmqctl/cmd/mcp.go
index edaf7e8d8..e994e83ff 100644
--- a/rmqctl/cmd/mcp.go
+++ b/rmqctl/cmd/mcp.go
@@ -17,6 +17,8 @@
 package cmd
 
 import (
+       "path/filepath"
+
        "github.com/apache/rocketmq-dashboard/rmqctl/internal/output"
        "github.com/apache/rocketmq-dashboard/rmqctl/internal/studio"
        "github.com/spf13/cobra"
@@ -65,7 +67,12 @@ func (a *App) newMCPConfigCommand(runtime commandRuntime) 
*cobra.Command {
                        out := cmd.OutOrStdout()
                        stdioArgs := []string{"mcp", "stdio"}
                        if runtime.options.configPath != "" {
-                               stdioArgs = append(stdioArgs, "--config", 
runtime.options.configPath)
+                               // The MCP client may launch from a different 
working directory.
+                               configPath, err := 
filepath.Abs(runtime.options.configPath)
+                               if err != nil {
+                                       return err
+                               }
+                               stdioArgs = append(stdioArgs, "--config", 
configPath)
                        }
                        if runtime.options.context != "" {
                                stdioArgs = append(stdioArgs, "--context", 
runtime.options.context)
diff --git a/rmqctl/cmd/mcp_config_golden_test.go 
b/rmqctl/cmd/mcp_config_golden_test.go
index ea6a0d622..8381ce836 100644
--- a/rmqctl/cmd/mcp_config_golden_test.go
+++ b/rmqctl/cmd/mcp_config_golden_test.go
@@ -19,6 +19,7 @@ package cmd
 import (
        "bytes"
        "encoding/json"
+       "path/filepath"
        "reflect"
        "testing"
 
@@ -79,6 +80,14 @@ func TestMCPConfigMatchesStudioGeneratedSnippet(t 
*testing.T) {
        if err := json.Unmarshal([]byte(studioJavaMcpSnippet), &expected); err 
!= nil {
                t.Fatalf("decode the Studio snippet: %v", err)
        }
+       // The server fixture uses a Unix path. On Windows that path is rooted 
on
+       // the current drive; the CLI makes the drive explicit for a later 
launch.
+       configPath, err := filepath.Abs("/tmp/c.yaml")
+       if err != nil {
+               t.Fatal(err)
+       }
+       expectedServer := 
expected.(map[string]any)["mcpServers"].(map[string]any)["rocketmq-studio"].(map[string]any)
+       expectedServer["args"].([]any)[3] = configPath
        if !reflect.DeepEqual(emitted, expected) {
                t.Fatalf("mcp config drifted from the Studio-generated 
snippet:\n got:  %s\n want: %s",
                        stdout, studioJavaMcpSnippet)
@@ -103,7 +112,7 @@ func TestMCPConfigMatchesStudioGeneratedSnippet(t 
*testing.T) {
        if _, found := entry["env"]; found {
                t.Fatalf("the snippet must not carry an env block: %s", stdout)
        }
-       wantArgs := []any{"mcp", "stdio", "--config", "/tmp/c.yaml", 
"--instance-id", "x", "--timeout", "1m0s"}
+       wantArgs := []any{"mcp", "stdio", "--config", configPath, 
"--instance-id", "x", "--timeout", "1m0s"}
        if !reflect.DeepEqual(entry["args"], wantArgs) {
                t.Fatalf("args = %v, want %v (argv order is the contract)", 
entry["args"], wantArgs)
        }
@@ -140,7 +149,11 @@ func TestMCPConfigOmitsDefaultTimeout(t *testing.T) {
                        t.Fatalf("the default timeout must be omitted, got %v", 
args)
                }
        }
-       wantArgs := []any{"mcp", "stdio", "--config", "/tmp/c.yaml", 
"--instance-id", "x"}
+       configPath, err := filepath.Abs("/tmp/c.yaml")
+       if err != nil {
+               t.Fatal(err)
+       }
+       wantArgs := []any{"mcp", "stdio", "--config", configPath, 
"--instance-id", "x"}
        if !reflect.DeepEqual(args, wantArgs) {
                t.Fatalf("args = %v, want %v", args, wantArgs)
        }
diff --git a/rmqctl/cmd/mcp_config_path_test.go 
b/rmqctl/cmd/mcp_config_path_test.go
new file mode 100644
index 000000000..e4b66ebc5
--- /dev/null
+++ b/rmqctl/cmd/mcp_config_path_test.go
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package cmd
+
+import (
+       "bytes"
+       "encoding/json"
+       "path/filepath"
+       "reflect"
+       "testing"
+)
+
+func TestMCPConfigResolvesSelectedPathBeforeClientLaunch(t *testing.T) {
+       t.Chdir(t.TempDir())
+       for _, supplied := range []string{"config.yaml", filepath.Join("配置 
files", "config.yaml"), filepath.Join(t.TempDir(), "absolute.yaml")} {
+               t.Run(supplied, func(t *testing.T) {
+                       stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
+                       app := NewApp(stdout, stderr)
+                       app.Store.Getenv = emptyEnv
+                       if code := app.Execute([]string{"mcp", "config", 
"--config", supplied}); code != 0 {
+                               t.Fatalf("exit = %d: %s", code, stderr)
+                       }
+                       var snippet struct {
+                               Servers map[string]struct {
+                                       Args []string `json:"args"`
+                               } `json:"mcpServers"`
+                       }
+                       if err := json.Unmarshal(stdout.Bytes(), &snippet); err 
!= nil {
+                               t.Fatal(err)
+                       }
+                       args := snippet.Servers["rocketmq-studio"].Args
+                       expected, err := filepath.Abs(supplied)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+                       if !reflect.DeepEqual(args, []string{"mcp", "stdio", 
"--config", expected}) {
+                               t.Fatalf("args = %q; want selected absolute 
config %q", args, expected)
+                       }
+                       // A client can launch in another working directory 
without changing the selected file.
+                       t.Chdir(t.TempDir())
+                       resolved, err := filepath.Abs(args[3])
+                       if err != nil || resolved != expected {
+                               t.Fatalf("client resolves %q, want %q: %v", 
resolved, expected, err)
+                       }
+               })
+       }
+}
+
+func TestMCPConfigLeavesUnspecifiedPathUnbound(t *testing.T) {
+       stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
+       app := NewApp(stdout, stderr)
+       app.Store.Getenv = emptyEnv
+       if code := app.Execute([]string{"mcp", "config"}); code != 0 {
+               t.Fatalf("exit = %d: %s", code, stderr)
+       }
+       if bytes.Contains(stdout.Bytes(), []byte("--config")) {
+               t.Fatalf("unexpected config binding: %s", stdout)
+       }
+}
diff --git a/rmqctl/cmd/mcp_stdio_test.go b/rmqctl/cmd/mcp_stdio_test.go
index 6f890901a..fedc0f6ae 100644
--- a/rmqctl/cmd/mcp_stdio_test.go
+++ b/rmqctl/cmd/mcp_stdio_test.go
@@ -18,6 +18,8 @@ package cmd
 
 import (
        "bytes"
+       "encoding/json"
+       "path/filepath"
        "strings"
        "testing"
 
@@ -38,7 +40,12 @@ func TestMCPConfigEmitsOnlyContextAndConfigReferences(t 
*testing.T) {
                t.Fatal(err)
        }
        output := stdout.String()
-       for _, want := range []string{`"--config"`, `"/tmp/rmqctl.yaml"`, 
`"--context"`, `"prod"`} {
+       configPath, err := filepath.Abs("/tmp/rmqctl.yaml")
+       if err != nil {
+               t.Fatal(err)
+       }
+       encodedPath, _ := json.Marshal(configPath)
+       for _, want := range []string{`"--config"`, string(encodedPath), 
`"--context"`, `"prod"`} {
                if !strings.Contains(output, want) {
                        t.Fatalf("config missing %s: %s", want, output)
                }

Reply via email to