前言
本文将带你使用 ollama + mcp-go + cherry-studio 在本地实现并构建完整的一套流程。
在 MCP 的风吹了那么久之后,相信你已经看过不少介绍了,如果你还没有实际体验过 MCP 的魅力,又或者你想通过 Golang 构建一个 MCP 服务,那么就来看看吧。
准备
解释
首先快速白话解释一下什么是 MCP ?
在没有 MCP 的时候
用户:大模型,帮我点个外卖
大模型:好的,我将告诉你,点外卖的步骤是….但你得自己点,因为我没有“手”
当有了 MCP 之后
用户:大模型,帮我点个外卖
大模型:好的,鳄了 MCP 服务去帮我下单一杯咖啡
鳄了 MCP 服务:收到,咖啡订单已提交
所以,其实你不用了解太多的细节,也能知道 MCP 是做什么用的。关键就是,大模型本身仅提供了对话的能力,而想要实际操作一些东西的时候,它无法触及,而此时外部系统通常的做法是提供一些 API 接口,让其他系统能够调用自己,从而提供服务。而 API 的问题在于每家都有自己的参数列表和返回结果,MCP 的优势是在于制定了协议统一了接入的规范,从而让各个系统都能轻松的被大模型调用。从宏观的角度看,就是给大模型装上了 “手” 让他能触及实际的业务场景。
目标 我网上看了很多有关 MCP 实现案例的文章,发现一个共同的问题,大部分都在提供了一个工具服务为加法,a+b = c 这样。但是这完全体现不出 MCP 的意义(我的大模型自己不会算吗?非得你 MCP 教我?[狗头])。所以我们这次的目标是让大模型可以直接操作你本地的文件系统,从而体会到 MCP 的魅力。
直接上代码 不搞哪些花里胡哨的东西,直接上代码,而且非常简单,一看就懂。
首先定义两个方法,用于操作本地的目录文件
listFiles 用于查询目录下的所有文件
renameFile 由于重命名一个文件directoryPath
设置了仅允许操作的文件目录,防止大模型看到一些不该看的小视频
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package mainimport ( "errors" "fmt" "os" "path/filepath" "strings" ) const ( directoryPath = "/tmp/linkinstar" ) func listFiles (directory string , extension string ) ([]string , error ) { if strings.TrimSpace(directory) != directoryPath { return nil , errors.New(fmt.Sprintf("我无法访问目录 %s" , directory)) } fmt.Println("Listing files in directory :" , directory) files, err := os.ReadDir(directory) if err != nil { return nil , err } var result []string for _, file := range files { if file.IsDir() { continue } if extension == "" || strings.HasSuffix(file.Name(), extension) { result = append (result, file.Name()) } } return result, nil } func renameFile (directory string , oldName string , newName string ) ([]string , error ) { if strings.TrimSpace(directory) != directoryPath { return nil , errors.New(fmt.Sprintf("我无法访问目录 %s" , directory)) } fmt.Println("Renaming file:" , oldName, "to" , newName) err := os.Rename(filepath.Join(directory, oldName), filepath.Join(directory, newName)) if err != nil { return nil , err } return listFiles(directory, "" ) }
main.go 将两个 tools 注册并添加到服务中,最后启动了一个 SSE 的服务。注册时可以看到我们指定了输入的必要参数。具体这里就不过多解释 SSE 是什么了,当然 MCP 也提供了其他接入的方式,比如标准的输入输出等等,这里以 SSE 举例。AddTool
内的实现方法也非常简单,就是获取参数,调用前一步的 方法 ,然后处理并返回结果即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 package mainimport ( "context" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) func main () { s := server.NewMCPServer("File manager server" , "1.0.0" ) listFilesTool := mcp.NewTool("list_files" , mcp.WithDescription("列出指定目录下的文件" ), mcp.WithString("directory" , mcp.Required(), mcp.Description("要列出文件的目录" )), mcp.WithString("extension" , mcp.Description("要过滤的文件扩展名" )), ) s.AddTool(listFilesTool, func (ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error ) { directory := request.Params.Arguments["directory" ].(string ) extension := request.Params.Arguments["extension" ].(string ) files, err := listFiles(directory, extension) if err != nil { return mcp.NewToolResultText(err.Error()), nil } res := "文件列表:\n" for _, file := range files { res += file + "\n" } return mcp.NewToolResultText(res), nil }) renameFileTool := mcp.NewTool("rename_file" , mcp.WithDescription("重命名文件" ), mcp.WithString("directory" , mcp.Required(), mcp.Description("要重命名文件的目录" )), mcp.WithString("old_name" , mcp.Required(), mcp.Description("要重命名的旧文件名" )), mcp.WithString("new_name" , mcp.Required(), mcp.Description("新的文件名" )), ) s.AddTool(renameFileTool, func (ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error ) { directory := request.Params.Arguments["directory" ].(string ) oldName := request.Params.Arguments["old_name" ].(string ) newName := request.Params.Arguments["new_name" ].(string ) files, err := renameFile(directory, oldName, newName) if err != nil { return nil , err } res := "重命名后的文件列表:\n" for _, file := range files { res += file + "\n" } return mcp.NewToolResultText(res), nil }) err := server.NewSSEServer(s).Start(":9999" ) if err != nil { panic (err) } return }
测试
测试前请保证大模型本身支持 MCP 并正常运行,我使用的是:ollama run qwen2.5:7b
配置 MCP 服务 配置非常简单,只需要配置一个 http://127.0.0.1:9999/sse
地址就可以了
记得需要在对话前启用指定的 MCP 服务哦
对话测试 如果你可以看到在对话中客户端主动调用了你的 MCP 服务证明成功了
可以看到,大模型可以理解我们的要求,并调用对应所需要的 MCP 服务从而实现对应的操作。现在大模型的手已经可以伸到我们本地来咯。
扩展与总结 扩展 除了我们上面案例中提到通过 AddTool
方法告诉大模型你提供了哪一些工具,另外还有 AddResource
AddPrompt
提供可访问的资源以及最佳实践的一些提示词等。
在上面的案例中我们只是简单的列表和重命名了本地的文件,你可以进一步扩展,比如制作一个本地的文件自动管理工具,自动将杂乱无序的文件以一种合理的顺序归类并整理好。
总结 就像前面提到的那样,MCP 就像是给大模型装上了 “手” ,大脑(大模型)负责处理我们说的指令,将指令拆分成各个动作,然后调用各个协调系统(MCP)最终完成这个指令的工作 。相信你看完本文应该不仅能快速上手 MCP 的使用,还能体会到 MCP 的魅力所在。那么赶紧试试吧,去构建你自己的 MCP 服务吧。