MCP开发实战-如何使用MCP真正加速UE项目开发

本文原文地址 mp.weixin.qq.com 腾讯程序员@腾讯技术工程 发布,侵权删除

作者:hanzo

用说人话的方式讲解MCP

目前各种MCP的文章和实际例子以及开源工具层出不穷,本文试图用最简单的方式解释下MCP解决什么问题和MCP怎么写的问题。

为啥要用MCP

MCP是一项专为LLM工具化操作设计的轻量化标准协议,其核心目标是构建LLM与异构软件系统间的通用指令交互框架。与传统的单一功能调用机制不同,MCP通过三层架构创新解决工具扩展性问题:

【协议定位】

作为中间协议层,MCP抽象出独立于具体LLM和业务系统的接口描述层,允许开发者在不同维度(功能权限、输入格式、执行环境)对工具接口进行灵活管控,避免传统方案中接口爆炸带来的维护难题。

【技术架构】

  1. 接口描述层:采用声明式DSL定义工具元数据,包括功能语义、入参Schema、权限策略和执行上下文

  2. 代理控制层:内置动态路由引擎和权限验证模块,支持热插拔式工具注册与版本管理

  3. 协议适配层:提供跨平台SDK,自动生成OpenAPI/Swagger等标准接口文档

【核心优势】

  • 双向解耦:前端LLM无需感知具体工具实现,后端系统可独立迭代

  • 权限纵深:细粒度控制工具可见性(开发者/用户/模型层级)

  • 执行沙箱:支持Docker/WASM等多重运行时隔离方案

  • 生态兼容:自带LangChain/LLamaIndex等主流框架的适配器

综合上述的专业表述,说人话就是,只要你的LLM有Prompt遵循能力,那么不管你是qwen,llama,DeepSeek还是claude ,都可以连接同样的MCP Server并且让你的LLM能够真正的调用工具,因此大大加速了LLM工具使用的开发速度。

为什么最近MCP爆发了?

最近大量MCP的爆发依赖于LLM本身两个能力的大幅度提升:

1.结构化输出能力

2.指令遵循能力。特别是claude3.7 sonnets之后的进展,使得工具的使用成功率大幅提升。对于LLM本身的能力进展来说,通过工具使用的方式积累真实世界的数据,并且进行后训练,也会成为LLM的垂直能力和LLM工作准确率进一步提升的关键。

MCP Server开发实战

有了基础概念之后,我们就可以直接开始一个MCP Server的开发了,目前MCP官方提供四种语言的开发SDK,包括Python,typescript,java和kotlin。我们以IEG最常用的typescript为例构建工程。

在开始前我们先明确一些概念,通常,我们编写的MCP是一个MCP Server,在Server中我们通常会定义一系列我们所需要的工具。使用各种LLM的客户端只要能连接上Server,就可以使用我们的MCP的各种工具调用能力了。

在UE开发中,UE废物一样的文档和天量的代码经常让人头大,那么能不能让LLM帮我来分析代码呢?结合Emacs常用的tree-sitter语法分析库和MCP,我们就可以用LLM来做这件事。

(本工程基于github:github.com/ayeletstu..... 进行修改得来,由于原工程已经无法配置运行,我已经将修改后的代码传至 git.woa.com/IEG-RED-...)

首先,我们和普通配置NodeJs工程一样,在Package.json中添加相应依赖:

perl 复制代码
"dependencies": {  
    "@modelcontextprotocol/sdk": "0.6.0",  
    "glob": "^8.1.0",  
    "tree-sitter": "^0.20.1",  
    "tree-sitter-cpp": "^0.20.0"  
  },  
  "devDependencies": {  
    "@types/glob": "^8.1.0",  
    "@types/jest": "^29.5.14",  
    "@types/node": "^18.15.11",  
    "jest": "^29.7.0",  
    "ts-jest": "^29.2.5",  
    "typescript": "^5.0.4"  
  }  

可以看到我们所需要的modelcontextprotocol sdk和tree-sitter等都可以直接从npm下载配置,我们按照常理执行npm install等步骤。接下来和通常的NodeJS程序一样,我们编写index.ts文件,先导入mcp相关的接口:

javascript 复制代码
import { Server } from '@modelcontextprotocol/sdk/server/index.js';  
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';  
import {  
  CallToolRequestSchema,  
  ErrorCode,  
  ListToolsRequestSchema,  
  McpError,  
} from '@modelcontextprotocol/sdk/types.js';  

这里我们可以看到MCP的几个关键概念:

  • Server:我们的MCP服务器,也就是处理一类任务的工具集合

  • stdioServertransport:MCP默认用的通讯格式。

  • RequestSchema:使用MCP时需要提供的参数,名字等等信息。

首先,我们需要定义一个Server class

typescript 复制代码
class UnrealAnalyzerServer {  
  private server: Server;  
  private analyzer: UnrealCodeAnalyzer;  
 ......  
  }  
  
  public async start() {  
    try {  
      // Setup handlers first  
      this.setupToolHandlers();  
        
      // Connect to stdio transport  
      const transport = new StdioServerTransport();  
      await this.server.connect(transport);  
        
      console.log('Unreal Analyzer Server started successfully');  
    } catch (error) {  
      console.error('Failed to initialize server:', error);  
      process.exit(1);  
    }  
  }  

这里定义了我们的server的一些最常用的初始化流程和工具定义过程,因为我们是希望用MCP来分析代码,因此我们的CodeAnalyzer也属于我们的Server Class Member。

要定义工具,我们首先需要结合ListToolsRequestSchema来绑定我们的tools,参考下面的代码:

php 复制代码
private setupToolHandlers() {  
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({  
      tools: [  
        {  
          ......  
        {  
          name: 'analyze_class',  
          description: 'Get detailed information about a C++ class',  
          inputSchema: {  
            type: 'object',  
            properties: {  
              className: {  
                type: 'string',  
                description: 'Name of the class to analyze',  
              },  
            },  
            required: ['className'],  
          },  
        },  
  .....  

我们定义工具的名字,和工具所需要的输入,并将其绑定到server。这些信息会让MCP识别到我们需要调用到什么工具,并且在调用工具时,需要提供什么样的参数。

有了工具的名字和参数,MCP需要知道具体如何去执行我们想要的操作,比如分析C++类,搜索代码等等,这里就要用到callToolRequestSchema结构体:

csharp 复制代码
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {  
      // Only check for initialization for analysis tools  
      const analysisTools = ['analyze_class', 'find_class_hierarchy', 'find_references', 'search_code', 'analyze_subsystem', 'query_api'];  
      if (analysisTools.includes(request.params.name) && !this.analyzer.isInitialized() &&   
          request.params.name !== 'set_unreal_path' && request.params.name !== 'set_custom_codebase') {  
        throw new Error('No codebase initialized. Use set_unreal_path or set_custom_codebase first.');  
      }  
  
      switch (request.params.name) {  
       .....  
        case 'search_code':  
          return this.handleSearchCode(request.params.arguments);  
        case 'analyze_subsystem':  
          return this.handleAnalyzeSubsystem(request.params.arguments);  
        case 'query_api':  
          return this.handleQueryApi(request.params.arguments);  
        default:  
          throw new Error(`Unknown tool: ${request.params.name}`);  
      }  
    });  
  }  

很明显,callToolRequestSchema会将tools的名字和参数传给工具真正的执行者。在我们的Server内部定义的工具函数中,会调用tree-sitter cpp库 去进行真正的分析,然后将结果返回给我们的LLM进行总结。

来总结下, MCP的编写本身是非常简单的,我们需要实现的是定义工具的名字,参数(从LLM中自然语言的方式获取),以及用代码描述的真正执行工具的流程,并且将这些都绑定到我们的Server上,我们只需要关心我们在调用什么工具和我们需要什么数据就行了,至于给大模型的提示词,多轮对话暂存,格式化输出验证等需要考虑到问题,MCP的SDK都能帮我们搞定。

接下来我们用tsc编译我们的Nodejs程序,我们的Server就做好了。

使用MCP

读到这里细心的读者肯定会发现。我们的LLM在哪里?这就是MCP更重要的一个好处,它的Server是LLM无关的,只要客户端使用的LLM看得懂提示词,那么它就能使用同一个MCP Server。

接下来我们配置客户端来使用我们的MCP Server,目前很多软件,包括Claude desktop,dify等都支持了MCP,这里我们选择VSCode 的Cline插件作为客户端(因为他开源),安装和配置Cline的过程在此不再赘述,打开Cline的Setting,点击MCP Servers的按钮,我们会在下方看到一个Configure MCP Server的按钮,点击我们就可以打开我们的MCP设置Json:

前文提到过,MCP支持多种语言开发,包括Python,Typescript等,因此可以看到我们的MCP配置中也支持多种入口,一个MCP Server的入口,可以是Python脚本,可以是bat批处理,也可以是nodejs程序的入口,对于我们的server来说,我们要配置的是一个nodejs的入口程序,如下面的代码:

json 复制代码
"unreal-analyzer": {  
      "command": "node",  
      "args": [  
        "C:/Users/admin/Documents/Cline/MCP/unreal-analyzer-mcp/build/index.js"  
      ],  
      "env": {},  
      "disabled": false,  
      "autoApprove": [],  
      "timeout": 3600    }  

我们将入口指向我们编译好的JavaScript文件,保存好之后,Cline就会自动去执行这个index.js,如果有运行错误,那么Cline的设置窗口中会报错,当出现下面的绿色按钮时,则证明我们的MCP Server连接成功了:

接下来,我们就可以用自然语言的方式快速分析UE代码了,我们在Cline的对话框中切换到Act Mode(只有Actmode可以调用MCP Server),然后按照我们日常和同事交流说话的口吻打字:先告诉他我们的UE代码在哪里:

直接说:我想分析下UMaterialExpressionPanner这个类,LLM会分析你的需求,自己去调用工具:

同时,LLM也会根据自己的思考去继续调用工具,比如我的这个问题,它会继续调用工具,去搜索代码:

当他发现代码非常多的时候,它会考虑到:OK,我可能需要过滤一下代码,于是它会调用SearchWithContext的工具:

来个稍微复杂点的任务,让它帮我找找lumen里AO相关的类:

通过这个很简单的例子工程,我们可以总结出MCP的特点,MCP通过工程化的方法和统一的协议,给LLM装上了使用工具的手,这样我们的AI就可以真正的替我们干活。

真正的UnrealMCP实现

理解了MCP的工作逻辑和原理之后, 再开发垂直领域的MCP工具就会相对简单。接下来我们来分析下真正的能干活的UnrealMCP(github.com/kvick-gam.....)是怎么工作的。同时也展示下Python SDK下的MCP工作流。

UnrealMCP由两部分组成,一部分是MCP Server的Python代码,一部分是UE5的插件,其中UE5的插件主要负责对接我们操作UE需要的一些C++逻辑。这部分的安装逻辑和通常的UE插件完全一样。

而MCP本身的部分,我们希望能够在Cline插件中调用,由于这个UnrealMCP只能在Claude中工作,而Claude在国内使用非常麻烦,因此在Cline中配置本MCP时,我们需要对仓库上说明的配置文件稍微进行些修改。 在运行安装python, 启动venv等工作之后,我们在配置Cline MCP的json时,需要按照如下代码配置:

swift 复制代码
"unreal": {  
      "command": "cmd.exe",  
      "args": [  
        "/c",   
        "F:\\UnrealEngine\\Engine\\Plugins\\UnrealMCP\\MCP\\run_unreal_mcp.bat"  
      ],  
      "env": {  
        "PYTHONPATH": "F:\\UnrealEngine\\Engine\\Plugins\\UnrealMCP\\MCP\\python_modules",  
        "PATH": "${PATH};F:\\UnrealEngine\\Engine\\Plugins\\UnrealMCP\\MCP\\python_env\\Scripts"  
      },  
      "disabled": false,  
      "autoApprove": [],  
      "cwd": "F:\\UnrealEngine\\Engine\\Plugins\\UnrealMCP\\MCP"  
    },  

这样就可以在Cline中使用UnrealMCP了。

使用上,UnrealMCP也是非常简单的,打开UE,启动UnrealMCP插件之后,我们告诉LLM我们的需求:在场景中创建一个迷宫关卡:

MCP会将相关信息包装成Python调用脚本,发给UnrealMCP Server

我们就可以得到最终结果:

和之前的例子一样,开发UnrealMCP还是遵循定义工具名字,参数,定义行为的这几个步骤,在UnrealMCP中,它采用了:

  • Python MCP 服务

  • Python-C++ 桥接层

  • C++ Unreal Engine 插件

的三层架构来实现(因为UE的EditorPython并不是很完善,所以需要通过C++插件来实现命令的解析和工作)。以最简单的CreateObject为例子: 首先,依然是注册工具和参数,当然,使用Python SDK,这个过程会更加直接简单,通过Python的注解语法来进行:

less 复制代码
@mcp.tool()  
def create_object(ctx: Context, type: str, location: list = None, label: str = None) -> str:  
    params = {"type": type}  
    if location:  
        params["location"] = location  
    if label:  
        params["label"] = label  
    response = send_command("create_object", params)  

我们需要告诉UE我要创建的location和物体类型,接下来,用Python的socket通信封装一下LLM产生的数据,传给UE:

csharp 复制代码
def send_command(command_type, params=None, timeout=DEFAULT_TIMEOUT):  
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:  
        s.connect(("localhost", DEFAULT_PORT))  
        command = {"type": command_type, "params": params or {}}  
        s.sendall(json.dumps(command).encode('utf-8'))  
        response_data = receive_response(s)  
        return json.loads(response_data.decode('utf-8'))  

最后UE在C++插件中进行接受消息,去调用NewActor函数:

rust 复制代码
TSharedPtr<FJsonObject> FMCPCreateObjectHandler::Execute(const TSharedPtr<FJsonObject> &Params, FSocket *ClientSocket)  
{  
    // 从参数中提取数据  
    FString Type;  
    Params->TryGetStringField(FStringView(TEXT("type")), Type);  
      
    // 执行 Unreal Engine 操作  
    AStaticMeshActor *NewActor = World->SpawnActor<AStaticMeshActor>(...);  
      
    // 返回响应  
    TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();  
    ResultObj->SetStringField("name", NewActor->GetName());  
    return CreateSuccessResponse(ResultObj);  
}  

虽然整体流程复杂了些,但是可以看到,它依然遵循了MCP的设计范式,既通过Tools来扩展能力,告诉LLM,你可以去干什么事,然后用各种方法,将LLM分析自然语言后得出的指令转为工具调用,去做真正的工作。

MCP的局限性

MCP虽然非常简洁明了,大大方便了LLM Tool use的开发成本,但是从本质上来说,MCP只是解决了工具使用的可能性这一个主题,要想让AI真正干活,可以说MCP只是干活的那只手,我们同样需要大脑(规划Agent),记忆力(数据库,记事本)来共同辅助完成自动化的工作。

此外,对于真正的专业软件来说,每一个接口/功能对应MCP可能也是一个工程量不小的工作,MCP结合真正靠谱的Agent编程框架才有可能完成真正复杂的任务。

总结

本文通过两个案例,展示了MCP的整体开发逻辑和能力。通过MCP,大模型可以真正干活。但同时,MCP不应该被过度神话,它只是解决了工具调用这一系列的问题。要想让大模型彻底重塑日常的游戏开发工作流,还需要在流程,记忆力,以及模型本身的后训练上持续工作。

相关推荐
Tee xm3 小时前
清晰易懂的VSCode加Cline插件使用不同API提供商实现AI编程
vscode·ai编程·安装
renne8 小时前
SpringAI实践(四)
ai编程·cursor
网安刚哥8 小时前
MCP Server 牛刀小试之雷池WAF MCP
程序员·github·ai编程
沉默王贰10 小时前
一名Android开发者使用Cursor 打造了一款记账APP
ai编程·cursor·trae
Captaincc10 小时前
从LSP看MCP:协议标准化如何改变开发与AI生态
后端·mcp
Phodal12 小时前
AutoDev Next:IDE 即 AI 编程服务,构建多端粪围编程
程序员·ai编程
Aion_Liu17 小时前
应用实战|借力EdgeOne Pages搭建对话型AI网站
deepseek·mcp
huige999917 小时前
Cursor平替终极指南:用Augment与RooCode武装你的VS Code
ai编程
rainstop_317 小时前
从零搭建 MCP 服务的体验之旅
mcp
Captaincc1 天前
如何让 Cursor Agent 变得超强:完整指南
mcp