【日常随笔】基于MCP生态的LLM-Agent开发

本文主要内容搬运自本人CSDN以下博文,欢迎大家关注!

最近在AI大模型领域,MCP这个概念非常火,大大小小的公众号都开始对外炒作这个概念,宣教一种新的大模型Agent开发生态。因为工作原因,近期笔者也对MCP和LLM-Agent开发做了一些接触。因此今天这篇文章就浅聊下笔者对基于MCP的LLM-Agent开发方面,自己粗浅的一些理解。

首先还是聊一下什么是MCP,以及MCP在LLM-Agent开发方面,解决了什么问题。

MCP(Model Context Protocol,模型上下文协议)是由做Claude的公司制定的,其主要的作用是对大模型访问三方能力(OpenAPI、多模态数据源)定下规范。想象下,不管是本地部署的大模型还是remote的agent,或多或少都有访问本地DB、调用外部OpenAPI,或者获取外部的Prompt文本、图片等多模态资源的需求,而对于模型本身来讲,模型自己只能理解自然语言,反倒结构化的数据不好去理解,所以对于Agent开发者,还需要花很多精力去封装这些三方能力,才能够让模型做比较精确的意图识别。而MCP就通过一套协议约定,去解决这个问题,不同的三方能力用同一套描述方式做打标,并且在数据传输层也封装了stdio和sse两套方案,从而分别满足访问本地资源和访问外部资源的需求。

结合Tool use with Claude这篇文章,可以更好理解MCP要解决的问题。比如对于Claude大模型客户端,CallTool的过程,先是根据UserPrompt去识别决定要用哪个Tool,然后才是调用,拿到结果后处理成文本再返回。在这篇文档里面也可以看到每个Tool定义的样子,一般用name、description(用途)和inputSchema(入参标注),就可以满足模型做用途识别和参数识别的需要。

MCP协议相当于对大模型的前端(Agent应用)和后端(三方工具)做了分离,提供了标准化且具备安全性的协议,所以在这个基础上,MCP-Server的市场生态也衍生了出来。比如mcp.so,作为一个类似"网关"的角色,实现了大规模MCP-Server的在线托管,同样近期阿里云也提供了MCP广场的服务。看起来这种模式和传统的Web-OpenAPI市场比较类似,但不同点在于MCP主要面向大模型服务,能够提供多模态的数据,Web-OpenAPI并不重视这个,所以这个概念也不算非常重复。

对于Agent开发者而言,要封装三方工具,除了以往自主封装或者依靠框架约定做封装之外,现在也可以直接利用mcp-go之类的SDK,直接通过ListTools、CallTool等操作实现工具调用,并且各类框架现在也在逐渐跟进基于MCP协议的三方调用模式。从长远上来看,这种模式由很大的发展空间,但短期来看,这套连接的稳定可用性SLA保障基建其实还比较欠缺。所以个人建议对于Agent开发者来说,重点还是去打磨自己的Tool能力或Agent效果,但也要为长期把Tool迁移到MCP生态做准备。

那么基于MCP这套架构,具体Agent要怎么做代码开发呢?其实现在有很多现成的SDK可以用。比如笔者的话,因为工作原因,主要写Go语言,就接触到了mcp-go这套MCP的SDK实现。这套SDK满足了最基础的MCP实现,对于企业内部而言,在这个SDK上做封装,基本上就能够完善MCP-Server的开发生态。因此也简单看一下这个SDK里面,实现了什么东西。

首先是Client连接的实现,这里可以看到每次连接都需要InitializeRequest、InitializeResult以及InitializeNotification这三次握手。从Client角度看逻辑是这样:

go 复制代码
func (c *StdioMCPClient) Initialize(
	ctx context.Context,
	request mcp.InitializeRequest,
) (*mcp.InitializeResult, error) {
	// This structure ensures Capabilities is always included in JSON
	params := struct {
		ProtocolVersion string                 `json:"protocolVersion"`
		ClientInfo      mcp.Implementation     `json:"clientInfo"`
		Capabilities    mcp.ClientCapabilities `json:"capabilities"`
	}{
		ProtocolVersion: request.Params.ProtocolVersion,
		ClientInfo:      request.Params.ClientInfo,
		Capabilities:    request.Params.Capabilities, // Will be empty struct if not set
	}

	response, err := c.sendRequest(ctx, "initialize", params)
	if err != nil {
		return nil, err
	}

	var result mcp.InitializeResult
	if err := json.Unmarshal(*response, &result); err != nil {
		return nil, fmt.Errorf("failed to unmarshal response: %w", err)
	}

	// Store capabilities
	c.capabilities = result.Capabilities

	// Send initialized notification
	notification := mcp.JSONRPCNotification{
		JSONRPC: mcp.JSONRPC_VERSION,
		Notification: mcp.Notification{
			Method: "notifications/initialized",
		},
	}

	notificationBytes, err := json.Marshal(notification)
	if err != nil {
		return nil, fmt.Errorf(
			"failed to marshal initialized notification: %w",
			err,
		)
	}
	notificationBytes = append(notificationBytes, '\n')

	if _, err := c.stdin.Write(notificationBytes); err != nil {
		return nil, fmt.Errorf(
			"failed to send initialized notification: %w",
			err,
		)
	}

	c.initialized = true
	return &result, nil
}

握手的校验当前还比较粗糙,没有对版本号之类的兼容性做校验。两次握手后Client确认Notification(单向消息)可以发出去,就代表可以建立连接了。

从利于应用开发的角度,开发框架有SDK的话,最好是再封装一层Client把Initialize握手步骤也代理掉,然后把其他List/Call协议也封装成接口,这样对开发者比较方便一些。

然后看Server端的实现,主要包括:资源/Prompt/Tool的管理、C2S的Notification的处理,以及S2C单点Notification跟广播能力。说白了就是无状态、长连接都同时能支持上。

go 复制代码
// NewMCPServer creates a new MCP server instance with the given name, version and options
func NewMCPServer(
	name, version string,
	opts ...ServerOption,
) *MCPServer {
	s := &MCPServer{
		resources:            make(map[string]resourceEntry),
		resourceTemplates:    make(map[string]resourceTemplateEntry),
		prompts:              make(map[string]mcp.Prompt),
		promptHandlers:       make(map[string]PromptHandlerFunc),
		tools:                make(map[string]ServerTool),
		name:                 name,
		version:              version,
		notificationHandlers: make(map[string]NotificationHandlerFunc),
		capabilities: serverCapabilities{
			tools:     nil,
			resources: nil,
			prompts:   nil,
			logging:   false,
		},
	}

	for _, opt := range opts {
		opt(s)
	}

	return s
}

应用角度就比较简单了,Server端可以基于examples/everything/main.go的实现做扩展,Client端长期来看用SSE的连接方式比较多,参考client/sse_test.go的实现做扩充即可。

相关推荐
牛奶2 小时前
2026年大模型怎么选?前端人实用对比
前端·人工智能·ai编程
牛奶2 小时前
前端人为什么要学AI?
前端·人工智能·ai编程
EdisonZhou8 小时前
MAF快速入门(18)Agent Skill 快速开始
llm·aigc·agent
KEEN的创享空间8 小时前
AI编程从0到1之10X提效(Vibe Coding 氛围式编码 )09篇
openai·ai编程
AlienZHOU9 小时前
为 AI Agent 编写高质量 Skill:Claude 官方指南
agent·ai编程·claude
恋猫de小郭10 小时前
移动端开发稳了?AI 目前还无法取代客户端开发,小红书的论文告诉你数据
前端·flutter·ai编程
KaneLogger11 小时前
【翻译】打造 Agent Skills 的最佳实践
agent·ai编程·claude
王小酱11 小时前
Everything Claude Code 文档
openai·ai编程·aiops
雮尘12 小时前
如何在非 Claude IDE (TARE、 Cursor、Antigravity 等)下使用 Agent Skills
前端·agent·ai编程
会写代码的柯基犬12 小时前
DeepSeek vs Kimi vs Qwen —— AI 生成俄罗斯方块代码效果横评
人工智能·llm