uploads,tools,config,main: 将 upload_file 返回路径改为服务器端绝对路径并透传给 spark_submit
之前 upload_file 把文件写到 DataDir/uploads/<filename>,返回相对路径
uploads/hello.txt。MCP 服务器与 agent 不在同一台机器,agent 无法构造服务
器本地路径,因此 spark_submit 无法定位脚本。
改为由新的 internal/uploads 包 mint 一个 32 字符十六进制 file_id,文件落盘为
DataDir/uploads/<file_id>,元数据写入 <file_id>.meta.json(原始文件名只存在
sidecar 里)。upload_file 返回 {file_id, path, name, size, sha256},其中 path
是绝对路径。spark_submit 的 description 明确要求 LLM 直接把 upload_file 返回
的 path 放进 args,不要自己构造路径。
为什么只在描述里约束而不在代码里拒绝非 mint 的绝对路径:集群本地已有路径
(如 /opt/spark/examples/pi.py)是合法的 spark-submit 参数,代码不能替
LLM 拒绝。
测试锁定:
- internal/uploads: Save 往返、AbsPath/Validate 非法路径、Sweep 过期/未过期/
孤立 sidecar
- internal/mcp/tools: upload_file 新响应字段、spark_submit 透传 mint 路径与
集群本地路径
刻意未做:S3/HDFS 上传、给 spark_submit 新增 file_id 参数、在代码层面拒绝
集群本地绝对路径。
破坏性变更:upload_file 响应从相对 path 改为绝对 path,并新增 file_id/name/
sha256 字段。该工具尚无外部调用者。
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
|
||||
"spark-mcp-go/internal/executor"
|
||||
"spark-mcp-go/internal/uploads"
|
||||
)
|
||||
|
||||
const SparkSubmitName = "spark_submit"
|
||||
@@ -14,7 +15,7 @@ const SparkSubmitName = "spark_submit"
|
||||
// NewSparkSubmitTool returns the schema for the spark_submit MCP Tool.
|
||||
func NewSparkSubmitTool() mcp.Tool {
|
||||
return mcp.NewTool(SparkSubmitName,
|
||||
mcp.WithDescription("Submit a Spark application to a configured cluster using the cluster's local spark-submit binary. Returns {app_id, exit_code, stdout_tail, stderr_tail, duration_ms}. The cluster's default_submit_args are prepended to your args. Binary is invoked as a child process — no shell, no command injection."),
|
||||
mcp.WithDescription("Submit a Spark application to a configured cluster using the cluster's local spark-submit binary. The `args` array is appended to the cluster's default_submit_args; pass one argv element per CLI flag. If the script to run was just uploaded via upload_file, put the `path` field from that response as one of the args — do NOT construct your own path. Cluster-local paths (e.g. /opt/spark/examples/pi.py) are also accepted. Returns {app_id, exit_code, stdout_tail, stderr_tail, duration_ms}. Binary is invoked as a child process — no shell, no command injection."),
|
||||
mcp.WithString("cluster_id",
|
||||
mcp.Required(),
|
||||
mcp.Description("ID of the configured cluster (from list_clusters)"),
|
||||
@@ -63,7 +64,7 @@ func (d *Deps) SparkSubmitHandler(ctx context.Context, req mcp.CallToolRequest)
|
||||
}
|
||||
|
||||
fullArgs := append([]string{}, cl.DefaultSubmitArgs...)
|
||||
fullArgs = append(fullArgs, userArgs...)
|
||||
fullArgs = append(fullArgs, translateArgs(userArgs, d.UploadStore)...)
|
||||
|
||||
result, err := executor.Run(ctx, executor.SparkSubmitOpts{
|
||||
Binary: cl.SparkSubmitExecuteBin,
|
||||
@@ -98,3 +99,23 @@ func (d *Deps) SparkSubmitHandler(ctx context.Context, req mcp.CallToolRequest)
|
||||
})
|
||||
return textResult(encodeJSON(result)), nil
|
||||
}
|
||||
|
||||
// translateArgs canonicalizes server-minted upload paths and leaves
|
||||
// cluster-local paths untouched. It is a code-level guardrail, not a
|
||||
// validator: non-minted absolute paths are intentionally accepted.
|
||||
func translateArgs(userArgs []string, store *uploads.Store) []string {
|
||||
if store == nil {
|
||||
return userArgs
|
||||
}
|
||||
out := make([]string, len(userArgs))
|
||||
for i, arg := range userArgs {
|
||||
if fileID, err := store.Validate(arg); err == nil {
|
||||
if canonical, err := store.AbsPath(fileID); err == nil {
|
||||
out[i] = canonical
|
||||
continue
|
||||
}
|
||||
}
|
||||
out[i] = arg
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user