《MCP助手实战:构建远程文件下载工具》
通过 Tool 功能实现对目标链接文件保存到本地任意位置通过 Prompt提供交互模板通过 Resource 展示本地资源。
本文以Go语言为例,介绍如何开发一个类似"MCP助手"的远程文件下载工具。该工具能够从指定URL下载文件并保存到本地,具备进度显示、断点续传等实用功能。
使用框架:github.com/mark3labs/mcp-go/server
MCP客户端:Cherry Studio
一、工具功能概述
核心功能点:
- 通过 Tool 功能实现对目标链接文件保存到本地任意位置
- 通过 Prompt 提供交互模板
- 通过 Resource 展示本地资源
二、功能实现
搭建MCP服务器
声明MCP服务器, 有两种启动方式 Studio | SSE
对于Studio ,不需要端口信息,他的信息传入和输出都是走的进程管道标准输入和输出。
对于SSE,需要端口信息,他的信息传入和输出是基于HTTP服务提供的回话信息
我们通过MCP框架NewMCPServer创建一个MCP,声明了默认的日志输出行为和兜底逻辑。
import "video-download-mcp/internal/server"
....
// Create the application MCP server instance
srv := server.NewMCPServer()
...
case "stdio":
log.Println("Starting MCP server with stdio transport...")
go func() {
if err := server.RunMCPServerWithStdio(srv); err != nil {
log.Printf("MCP stdio server error: %v", err)
}
}()
case "sse":
log.Printf("Starting MCP server with SSE transport on port %d...", *port)
go func() {
if err := server.RunMCPServerWithSSE(srv, *port); err != nil {
log.Printf("MCP SSE server error: %v", err)
}
}()
default:
log.Fatalf("unknown transport: %s (expected 'stdio' or 'sse')", *transport)
}
// RunMCPServerWithStdio starts the MCP server using stdio transport
func RunMCPServerWithStdio(s *MCPServer) error {
return mcpserver.ServeStdio(s.server)
}
// RunMCPServerWithSSE starts the MCP server using SSE transport on the given port
func RunMCPServerWithSSE(s *MCPServer, port int) error {
sse := mcpserver.NewSSEServer(s.server)
addr := fmt.Sprintf(":%d", port)
return sse.Start(addr)
}
// NewMCPServer constructs a new MCP server and registers all tools
func NewMCPServer() *MCPServer {
// Create MCP server with basic middlewares
srv := mcpserver.NewMCPServer(
"video-download",
"1.0.0",
mcpserver.WithLogging(),
mcpserver.WithRecovery(),
)
return &MCPServer{server: srv}
}
通过MCP Tool功能实现对目标链接文件保存到本地任意位置
1、框架支持通过插件的方式声明MCPServer是否支持tool功能,我们通过NewTool方法,声明了我们服务其支持那种工具,他有以下三要素:
- 工具的名称
- 工具的描述
- 工具的参数
// NewMCPServer constructs a new MCP server and registers all tools
func NewMCPServer() *MCPServer {
....
// Register tools
tools.RegisterTools(srv)
return &MCPServer{server: srv}
}
// RegisterTools registers all MCP tools for the service
func RegisterTools(s *mcpserver.MCPServer) {
// Tool: download_video_file
// Required params: url, save_dir, filename
downloadTool := mcp.NewTool(
"download_video_file",
mcp.WithDescription("Download a video from URL and save to target directory with a given filename"),
mcp.WithString("url", mcp.Description("The video file URL (HTTP/HTTPS)"), mcp.Required()),
mcp.WithString("save_dir", mcp.Description("Directory where the video will be saved"), mcp.Required()),
mcp.WithString("filename", mcp.Description("Target filename (without path)"), mcp.Required()),
)
s.AddTool(downloadTool, handleDownloadVideoFile)
}
2、实现远程文件保存功能
类似于Gin, McpServet 添加一个Tool和一个Tool的回调函数,当tool被调用时,McpServer会回调我们的 handleDownloadVideoFile 逻辑
// handleDownloadVideoFile executes the download and returns the absolute saved path
func handleDownloadVideoFile(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
// Type-safe argument access – returns typed errors if wrong or missing
url, err := req.RequireString("url")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
saveDir, err := req.RequireString("save_dir")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
fileName, err := req.RequireString("filename")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
// Sanitize and compute target path
targetPath := filepath.Join(saveDir, fileName)
// Perform download via use case
savedPath, err := usecase.DownloadVideo(ctx, url, targetPath)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("Failed to download video: %v", err)), nil
}
// Return absolute path
return mcp.NewToolResultText(savedPath), nil
}
实现代码:
https://github.com/LaOzhOy1/video-download-mcp/commit/ac933e7ff767e0c968aeefde0388a5e6d3a8f0bf

通过Prompt能力,提供模板给用户输入
我们都知道,结构化的输入更有助于模型进行交互,调用mcp也不例外。
1、 声明一个Prompt对象,Tool的形式一样,它也有三要素:模板名称,模板描述,模板参数及其参数的解释
2、添加模板到MCP服务中
AddPrompt(p, func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error)
可以看到,这个方法有两个非常关键的点:
- mcp.GetPromptRequest, 其中request.Params.Arguments藏有大模型提供给mcp的参数
- mcp.NewPromptMessage,根据所给的参数,填入对应的模板中,返回一个系统的请求信息,告诉大模型要用什么工具去执行什么参数,这回带来一个好处,模型只要理解了Prompt的含义,就能根据我们预先规定的流程去执行逻辑,降低模型推理的不确定性(代理里只有一个download_video_file方法,如果有多个方法的调用顺序的话更明显)
// NewMCPServer constructs a new MCP server and registers all tools
func NewMCPServer() *MCPServer {
....
// Register tools
tools.RegisterTools(srv)
// Register prompts
prompt.RegisterPrompts(srv)
return &MCPServer{server: srv}
}
// registerPrompts defines MCP prompts that Cherry Studio can call to guide file downloads
func RegisterPrompts(s *mcpserver.MCPServer) {
// Prompt: download_file_prompt – guides clients to call the tool with required args
p := mcp.NewPrompt(
"download_file_prompt",
mcp.WithPromptDescription("Guide to download a file by URL to target directory with filename"),
mcp.WithArgument("url", mcp.ArgumentDescription("The video/file URL (HTTP/HTTPS)"), mcp.RequiredArgument()),
mcp.WithArgument("save_dir", mcp.ArgumentDescription("Destination directory to save the file"), mcp.RequiredArgument()),
mcp.WithArgument("filename", mcp.ArgumentDescription("Target filename to use for saving"), mcp.RequiredArgument()),
)
s.AddPrompt(p, func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
args := request.Params.Arguments
url := fmt.Sprintf("%v", args["url"]) // simple extraction; validated by client
dir := fmt.Sprintf("%v", args["save_dir"]) // required by prompt
name := fmt.Sprintf("%v", args["filename"]) // required by prompt
title := "Download File Instruction"
// Compose assistant message instructing client to invoke the tool with these args
msg := mcp.NewPromptMessage(
mcp.RoleAssistant,
mcp.NewTextContent(
fmt.Sprintf(
"Use tool 'download_video_file' with arguments: url=%s, save_dir=%s, filename=%s. This will download the file to the destination and return the absolute path.",
url, dir, name,
),
),
)
return mcp.NewGetPromptResult(title, []mcp.PromptMessage{msg}), nil
})
}
实现代码:
https://github.com/LaOzhOy1/video-download-mcp/commit/4223a91bf549eec4855788cbfaef893a4385eb5b
通过Resource展示已下载资源
服务器运行过程中下载的资源保存在本地,这里我们用简单的一个JSON文件作为工程的数据记录,在工程启动时加载数据文件,并在运行时维护这份数据
var (
mu sync.RWMutex
downloads []string
dbPath = filepath.Join(".", "downloads_db.json")
)
func init() {
_ = load()
}
func RecordDownload(path string) error {
mu.Lock()
defer mu.Unlock()
// avoid duplicates
for _, p := range downloads {
if p == path {
return nil
}
}
downloads = append(downloads, path)
return save()
}
func ListDownloads() []string {
mu.RLock()
defer mu.RUnlock()
out := make([]string, len(downloads))
copy(out, downloads)
return out
}
每当大模型调用保存文件的功能时,调用RecordDownload方法记录数据,并返回保存结果。
// handleDownloadVideoFile executes the download and returns the absolute saved path
func handleDownloadVideoFile(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
...
// Perform download via use case (with progress callback placeholder)
savedPath, derr := usecase.DownloadVideoWithProgress(ctx, url, targetPath, func(written int64, total int64) {})
...
// Record successfully downloaded path
_ = storage.RecordDownload(savedPath)
// Return absolute path
return mcp.NewToolResultText(savedPath), nil
}
与Prompt和Tool一样,Resource也是MCP的原生支持的功能,我们需要告诉MCP客户端,服务端支持Resource能力。我们提供 downloads://list 接口给客户端调用,查询下载的结果,返回的类型时Json格式。
// RegisterResources registers server resources for clients to read
func RegisterResources(s *mcpserver.MCPServer) {
// Resource: list of downloaded file paths
res := mcp.NewResource(
"downloads://list",
"Downloaded Files",
mcp.WithResourceDescription("List of all downloaded files' absolute paths"),
mcp.WithMIMEType("application/json"),
)
s.AddResource(res, func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
paths := storage.ListDownloads()
data, err := json.Marshal(paths)
if err != nil {
return nil, err
}
return []mcp.ResourceContents{
mcp.TextResourceContents{
URI: "downloads://list",
MIMEType: "application/json",
Text: string(data),
},
}, nil
})
}

三、成果展示
在代码里我们实现一个Tool工具方法,提供给大模型调用保存视频到本地,调用这个工具方法之前,模型会根据Prompt 给与我们调用的入参提示,但是很遗憾,Cherry Studio 没有给Resource做交互界面,所以我们展示文件下载保存的流程:
1、输入内容告诉大模型我们要下载的视频链接和保存路径
帮我保存这个视频
视频链接: xxxx
保存文件名称: 测试视频
保存的文件路径:D:\资源爬取
2、 大模型接受用户的信息之前,cherry studio会对用户消息进行加工,告诉大模型一个Prompt,关于如何下载一个网络文件到本地的调用信息,还告诉大模型有一个MCP服务器Tool工具可用,这部分用户是不可见的,是内嵌在我们的Cherry studio当中的调用流程。
3、大模型解析加工后的消息数据后,会返回给Cherry Studio,告诉他要用什么工具进行下载以及调用这些工具所需要的参数,最终让Cherry Studio 调用对应方法进行视频下载。
结果:
大致流程图:
思考
MCP可以被HTTP接口取代吗
在详尽的为大模型描述下,HTTP接口也可以做到MCP类似的功能,但是MCP是一种大家斗公认的规范,是在HTTP接口上做了交互的约束,是一种针对大模型交互的标准,只有市面上的所有的MCP客户端遵守标准,才能让MCP更加适用。
多个MCP如何进行协调
每个MCP工具都有他自己的描述,大模型只会回答你提问的问题,MCP工具的调用,更多依赖于Client端的编排逻辑,比如:多轮对话循环,直到大模型不在进行工具调用后,再将最终的结果总结返回给用户,期间的MCP调用可以是并行的也可以是串行的,一句话而言:决策的东西让AI大模型做,工具编排的流程让Cilent来做。
更多推荐



所有评论(0)