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)
}