回收团队基于Cursor集成MCP的智能代码修复提示词生成实践

一、痛点

痛点一:为什么我的Token消耗的这么快

Token的消耗量通常受到下面两个因素的影响

  • 输入和输出的Token数量:输入的Token越多,输出的Token也会相应增加,因为模型会基于输入的上下文进行内容生成。
  • 任务复杂性:更复杂的任务通常需要消耗更多的Token,而多模态的Agent往往消耗的Token会更高。

为什么模糊的提示词会增加Token的消耗量?

  • 模糊的提示词可能包含大量与输出不相关的内容,增加输入的Token量。
  • 模糊的提示词通常缺乏具体细节或明确约束,导致模型无法直接理解用户意图。为了覆盖可能的解释,模型倾向于生成更长、更全面的响应,以避免遗漏关键信息,从而增加输出token数量。
  • 模糊的提示词往往无法精准的生成用户想要的内容。用户可能因输出不符合预期而多次修改提示,形成无效交互循环,无形中浪费Token。

痛点二:AI修复代码为什么总是"差点意思"?

一般都是拿到Sonar 扫描结果复制粘贴, 让AI修复后,一个一个改, 又太慢, 太多问题要么修复不准确,要么把好的代码也改坏了,还有就是问题 太多, 上下文超限修改失败

举个真实的例子: Sonar报告:ActivityServiceImpl.java 第120行存在空指针风险

我的提示词(第一版):

css 复制代码
请修复以下问题:第120行存在空指针风险
代码:
[粘贴整个ActivityServiceImpl.java文件,可能是几千行]

AI的回复:

  • 情况1:"这个文件太长了,请提供更具体的代码片段"
  • 情况2:修复了空指针,但把旁边的业务逻辑也改了
  • 情况3:"Token超出限制,无法处理"

问题根源在哪?

  1. 上下文冗余:超大文件代码中,真正相关的可能只有20行
  2. 缺乏规范引导:没告诉AI要遵循什么编码规范
  3. 信息不准确:没有精确指出问题在哪个方法、什么结构中
  4. 没有示例:AI缺乏类似问题的修复参考

二、转机:Cursor结合MCP打造自动化修复流程

关键问题在于:如何将Sonar扫描结果转换为AI能高效理解的结构化提示词

于是,我利用MCP(Model Context Protocol)协议为Cursor开发了一套智能提示词生成系统。

什么是MCP?

MCP(Model Context Protocol)是一个开放协议,Cursor支持这个协议,允许我们通过Python等语言编写自定义工具,直接集成到Cursor的AI助手中。简单说就是:让AI拥有调用你自己编写的工具的能力

我的方案架构

三、核心方案:六步智能提示词生成法

这套方案的核心思想是:让AI精确理解问题,而不是让AI去猜测问题

整个系统基于以下6个步骤构建:

第一步:问题类型识别

python 复制代码
class IssueTypeClassifier:
    """将Sonar规则映射到问题类型"""
    
    RULE_MAPPING = {
        # 空指针问题
        "java:S2259": "NullPointer",
        "java:S2637": "NullPointer",
        
        # 资源泄露
        "java:S2095": "ResourceLeak",
        "java:S2093": "ResourceLeak",
        
        # 安全漏洞
        "java:S3649": "SQLInjection",
        "java:S2076": "CommandInjection",
        
        # 代码规范
        "java:S117": "NamingConvention",
        "java:S1192": "DuplicateString",
    }
    
    @staticmethod
    def classify(rule_key: str) -> str:
        """根据规则ID分类问题类型"""
        return IssueTypeClassifier.RULE_MAPPING.get(
            rule_key, 
            "General"
        )

第二步:AST代码结构解析

什么是AST?

AST(Abstract Syntax Tree,抽象语法树)是将代码理解成树形结构的技术。

当Sonar告诉你"第120行有空指针问题",我们需要知道:

  • 第120行在哪个方法里?
  • 这个方法从哪行开始 ,到哪行结束
  • 方法的完整信息(方法名、参数、返回值)

AST解析 = 像编译器一样理解代码结构

想象代码是一个家族,AST就是家谱树:

yaml 复制代码
ClassDeclaration: ActivityServiceImpl(类)
 │
 ├─ FieldDeclaration: activityFacadeHelper(成员变量)
 │
 ├─ MethodDeclaration: queryActivityListWithNewImei(方法)
 │   ├─ position: line 106
 │   ├─ parameters: [Long orderId, String newMachineUuid]
 │   ├─ return_type: OrderAllowActivityMiVO
 │   └─ body: {...}  ← 第120行在这里
 │
 └─ MethodDeclaration: editActivityListWithNewImei(另一个方法)

使用javalang进行AST解析

javalang是一个纯Python库,用Python重新实现了Java语法解析器,不需要安装Java环境

python 复制代码
import javalang

class JavaCodeParser:
    """Java代码结构解析器 - 使用AST精确解析"""
    
    @staticmethod
    def extract_method_context(file_path: str, line_number: int) -> Optional[Dict]:
        """使用javalang进行AST解析,精确定位方法"""
        with open(file_path, 'r', encoding='utf-8') as f:
            code = f.read()
        
        # 1. 解析Java代码,生成AST(语法树)
        tree = javalang.parse.parse(code)
        
        # 2. 遍历AST,查找包含目标行的方法节点
        for path, node in tree.filter(javalang.tree.MethodDeclaration):
            method_start = node.position.line
            method_end = calculate_method_end(node)  # 计算方法结束位置
            
            # 3. 检查目标行是否在此方法范围内
            if method_start <= line_number <= method_end:
                return {
                    'class_name': extract_class_name(path),
                    'method_name': node.name,              # 方法名
                    'start_line': method_start,            # 方法开始行
                    'end_line': method_end,                # 方法结束行
                    'modifiers': node.modifiers,           # ['public', 'static']
                    'return_type': node.return_type.name,  # 返回值类型
                    'parameters': [p.name for p in node.parameters],  # 参数列表
                }
        
        return None

AST解析的价值

  1. 精确定位 :直接知道第120行在queryActivityListWithNewImei方法内,而不是editActivityListWithNewIme方法
  2. 完整信息:获取方法名、参数类型、返回值等元信息
  3. 语法理解:像编译器一样理解Java语法,不会被注释、字符串、Lambda表达式干扰
  4. 高准确率:准确率可达99%+

javalang的特点

  • 纯Python库,只需pip install javalang
  • 不需要安装JDK或Java环境
  • 提供完整的Java语法树节点
  • 支持Java 8+的语法特性

第三步:智能上下文提取

python 复制代码
class ContextExtractor:
    """根据问题类型智能提取代码上下文"""
    
    # 不同问题类型的提取策略配置
    EXTRACTION_RULES = {
        "NullPointer": {
            "range": "full_method",           # 提取完整方法
            "max_lines": 150,
            "include_imports": True,           # 包含import语句
            "include_class_declaration": True, # 包含类声明
        },
        "ResourceLeak": {
            "range": "full_method",
            "max_lines": 100,
            "include_imports": True,
            "include_class_declaration": True,
        },
        "SQLInjection": {
            "range": "full_method",
            "max_lines": 120,
            "include_imports": False,
            "include_class_declaration": True,
        },
        "NamingConvention": {
            "range": "surrounding",            # 只提取周围代码
            "max_lines": 30,
            "include_imports": False,
            "include_class_declaration": False,
        },
        "General": {
            "range": "surrounding",
            "max_lines": 50,
            "include_imports": False,
            "include_class_declaration": False,
        },
    }
    
    @staticmethod
    def extract(file_path: str, line_number: int, issue_type: str) -> str:
        """智能提取代码上下文"""
        rules = ContextExtractor.EXTRACTION_RULES.get(
            issue_type,
            ContextExtractor.EXTRACTION_RULES["General"]
        )
        
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                lines = f.readlines()
            
            context_lines = []
            
            # 1. 提取package和imports(如果需要)
            if rules.get("include_imports"):
                for line in lines[:50]:
                    if line.strip().startswith(('package ', 'import ')):
                        context_lines.append(line)
                    elif line.strip() and not line.strip().startswith('//'):
                        break
                if context_lines:
                    context_lines.append('\n')
            
            # 2. 提取方法或周围代码
            if rules["range"] == "full_method":
                method_info = JavaCodeParser.extract_method_context(file_path, line_number)
                if method_info:
                    start = method_info['start_line'] - 1
                    end = method_info['end_line']
                    
                    # 如果需要类声明,向上查找
                    if rules.get("include_class_declaration"):
                        for i in range(start, -1, -1):
                            if 'class ' in lines[i]:
                                context_lines.append(lines[i])
                                context_lines.append('    // ... 其他成员 ...\n\n')
                                break
                    
                    context_lines.extend(lines[start:end])
                else:
                    # 回退到周围代码
                    start = max(0, line_number - 25)
                    end = min(len(lines), line_number + 25)
                    context_lines.extend(lines[start:end])
            else:
                # 提取周围代码
                max_lines = rules["max_lines"]
                start = max(0, line_number - max_lines // 2)
                end = min(len(lines), line_number + max_lines // 2)
                context_lines.extend(lines[start:end])
            
            # 3. 限制总行数
            if len(context_lines) > rules["max_lines"]:
                context_lines = context_lines[:rules["max_lines"]]
                context_lines.append('\n// ... 后续代码省略 ...\n')
            
            return ''.join(context_lines)
            
        except Exception as e:
            logging.error(f"提取失败: {e}")
            return f"// 无法读取文件: {file_path}"

核心策略

  • 差异化提取:空指针问题提取150行含imports,命名规范只提取30行
  • 智能回退:AST解析失败时自动回退到简单提取
  • Token控制:通过max_lines限制,避免上下文过长

第四步:分层模板体系

python 复制代码
class PromptTemplateEngine:
    """分层的提示词模板引擎"""
    
    # Java语言规范
    JAVA_STANDARDS = """
请遵循以下Java开发规范:
1. 遵循阿里巴巴Java开发手册的编码规范
2. 所有可能为null的参数必须进行防御性检查
3. 资源(文件流、数据库连接等)必须使用try-with-resources自动管理
4. 异常必须妥善处理,不得吞掉异常
5. 方法复杂度不得过高,保持代码可读性
6. 变量和方法命名要清晰、符合驼峰命名规范
"""
    
    # 问题类型特定模板
    ISSUE_TEMPLATES = {
        "NullPointer": {
            "task": "修复以下代码中的空指针风险问题",
            "requirements": """
修复要求:
1. 在方法开始处对可能为null的参数进行检查
2. 如果参数为null,抛出IllegalArgumentException异常,并提供清晰的错误信息
3. 保持原有业务逻辑不变
4. 不要修改方法签名
5. 确保修复后不影响其他正常调用
"""
        },
        "ResourceLeak": {
            "task": "修复以下代码中的资源泄露问题",
            "requirements": """
修复要求:
1. 使用try-with-resources语句包裹需要关闭的资源
2. 确保在任何情况下(正常或异常)资源都能正确释放
3. 保持原有异常处理方式
4. 保持方法签名不变
"""
        },
        "SQLInjection": {
            "task": "修复以下代码中的SQL注入安全漏洞",
            "requirements": """
修复要求:
1. 必须使用参数化查询(PreparedStatement)替代字符串拼接
2. 使用占位符(?)和参数绑定传递用户输入
3. 不得使用字符串拼接或格式化方式构建SQL语句
4. 保持查询逻辑不变
"""
        },
    }
    
    @staticmethod
    def generate(issue_type: str, issue_data: dict, code_context: str, examples: str = "") -> str:
        """生成最终提示词"""
        
        template = PromptTemplateEngine.ISSUE_TEMPLATES.get(
            issue_type,
            {"task": "修复以下代码问题", "requirements": "保持代码风格与项目一致"}
        )
        
        # 组装提示词
        prompt = f"""你是一位精通Java开发的资深工程师。

任务:{template['task']}

编码规范:{PromptTemplateEngine.JAVA_STANDARDS.strip()}
"""
        
        # 添加示例(如果有)
        if examples:
            prompt += f"\n{examples}\n"
        
        # 添加问题详情
        prompt += f"""
问题详情:
- 规则ID: {issue_data.get('rule', 'Unknown')}
- 问题类型: {issue_type}
- 严重级别: {issue_data.get('severity', 'MAJOR')}
- 问题描述: {issue_data.get('message', '')}
- 问题位置: 第{issue_data.get('line', 0)}行

待修复代码:
```java
{code_context}
``` {data-source-line="360"}

{template['requirements'].strip()}

输出格式:
- 仅输出修复后的完整方法代码
- 使用```java```格式包裹代码
- 不要输出多余的解释文字
- 确保代码可以直接替换原有方法
"""
        
        return prompt

模板设计

  • 三层结构:角色设定 + 编码规范 + 问题类型特定要求
  • 灵活扩展:新增问题类型只需添加模板配置
  • 规范注入:自动注入阿里巴巴Java开发手册规范

第五步:Few-shot示例库

python 复制代码
class ExampleRepository:
    """问题修复示例库 - 提供修复前后的对比案例"""
    
    EXAMPLES = {
        "NullPointer": """
参考示例:

示例1 - 空指针防御性检查:
修复前:
```java
public String getUserEmail(User user) {
    return user.getEmail();
}
``` {data-source-line="395"}
修复后:
```java
public String getUserEmail(User user) {
    if (user == null) {
        throw new IllegalArgumentException("用户对象不能为空");
    }
    return user.getEmail();
}
``` {data-source-line="404"}

示例2 - 多参数空值检查:
修复前:
```java
public void processOrder(Order order, User user) {
    order.setUser(user);
    order.setStatus("processed");
}
``` {data-source-line="413"}
修复后:
```java
public void processOrder(Order order, User user) {
    if (order == null) {
        throw new IllegalArgumentException("订单对象不能为空");
    }
    if (user == null) {
        throw new IllegalArgumentException("用户对象不能为空");
    }
    order.setUser(user);
    order.setStatus("processed");
}
``` {data-source-line="426"}
""",
        "ResourceLeak": """
参考示例:

示例 - 使用try-with-resources:
修复前:
```java
public String readFile(String path) throws IOException {
    FileInputStream fis = new FileInputStream(path);
    byte[] buffer = new byte[1024];
    int length = fis.read(buffer);
    return new String(buffer, 0, length);
}
``` {data-source-line="440"}
修复后:
```java
public String readFile(String path) throws IOException {
    try (FileInputStream fis = new FileInputStream(path)) {
        byte[] buffer = new byte[1024];
        int length = fis.read(buffer);
        return new String(buffer, 0, length);
    }
}
``` {data-source-line="450"}
""",
        "SQLInjection": """
参考示例:

示例 - 使用PreparedStatement:
修复前:
```java
public User findUserByName(String name) {
    String sql = "SELECT * FROM users WHERE name = '" + name + "'";
    return jdbcTemplate.queryForObject(sql, User.class);
}
``` {data-source-line="462"}
修复后:
```java
public User findUserByName(String name) {
    String sql = "SELECT * FROM users WHERE name = ?";
    return jdbcTemplate.queryForObject(sql, User.class, name);
}
``` {data-source-line="469"}
""",
    }
    
    @staticmethod
    def get_examples(issue_type: str) -> str:
        """获取相关示例"""
        return ExampleRepository.EXAMPLES.get(issue_type, "")

示例库价值

  • Few-shot学习:让AI通过案例学习修复模式
  • 格式统一:修复前/后对比,清晰明了
  • 易于扩展:新增示例只需添加字符串配置

第六步:整合到MCP服务

现在,将所有组件整合到MCP服务中:

python 复制代码
from mcp.server import FastMCP
import httpx
import logging
import os

mcp = FastMCP("intelligent-code-fix")

class IntelligentPromptGenerator:
    """智能提示词生成器 - 整合6步生成法的核心类"""
    
    @staticmethod
    def generate_for_issue(issue: dict, base_dir: str) -> dict:
        """为单个issue生成智能提示词"""
        try:
            # 1. 提取issue信息
            rule_key = issue.get('rule', '')
            component = issue.get('component', '')
            file_path = component.split(':', 1)[1] if ':' in component else component
            full_path = os.path.join(base_dir, file_path)
            line_number = issue.get('line', 0)
            message = issue.get('message', '')
            severity = issue.get('severity', 'MAJOR')
            
            # 2. 问题类型识别
            issue_type = IssueTypeClassifier.classify(rule_key)
            
            logging.info(f"处理问题: {file_path}:{line_number} - {issue_type}")
            
            # 3. 提取代码上下文
            code_context = ContextExtractor.extract(full_path, line_number, issue_type)
            
            # 4. 获取示例
            examples = ExampleRepository.get_examples(issue_type)
            
            # 5. 生成提示词
            issue_data = {
                'rule': rule_key,
                'severity': severity,
                'message': message,
                'line': line_number,
            }
            
            prompt = PromptTemplateEngine.generate(
                issue_type=issue_type,
                issue_data=issue_data,
                code_context=code_context,
                examples=examples
            )
            
            # 6. 计算元数据
            token_estimate = len(prompt) // 4  # 粗略估算
            
            return {
                'success': True,
                'prompt': prompt,
                'metadata': {
                    'file': file_path,
                    'line': line_number,
                    'issue_type': issue_type,
                    'rule': rule_key,
                    'severity': severity,
                    'token_estimate': token_estimate,
                }
            }
            
        except Exception as e:
            logging.error(f"生成提示词失败: {e}")
            return {
                'success': False,
                'error': str(e),
                'file': file_path,
                'line': line_number,
            }

@mcp.tool("auto-fix-sonar-issues")
async def auto_fix_sonar_issues(
    project_name: str,
    cookie: str,
    base_dir: str,
    branch: str = None,
    page_size: int = 10,
    page_num: int = 1,
    is_new_code_period: bool = False
) -> str:
    """智能修复Sonar扫描问题 - 核心MCP工具"""
    
    # 获取Sonar问题列表
    async with httpx.AsyncClient() as client:
        params = {
            "components": project_name,
            "ps": page_size,
            "p": page_num,
        }
        if branch:
            params["branch"] = branch
        if is_new_code_period:
            params["inNewCodePeriod"] = "true"
        
        response = await client.get(
            "https://sonar.zhuanspirit.com/api/issues/search",
            params=params,
            headers={"Cookie": cookie},
            timeout=10.0
        )
        
        data = response.json()
        issues = data.get('issues', [])
        total = data.get('total', 0)
    
    # 为每个issue生成智能提示词
    results = []
    success_count = 0
    
    for idx, issue in enumerate(issues, 1):
        result = IntelligentPromptGenerator.generate_for_issue(issue, base_dir)
        
        if result['success']:
            success_count += 1
            metadata = result['metadata']
            results.append(f"""
{'='*80}
## Issue {idx}: {metadata['file']} (第{metadata['line']}行)

**问题类型**: {metadata['issue_type']}  
**严重级别**: {metadata['severity']}  
**预估Token**: {metadata['token_estimate']}

### 生成的智能提示词:

{result['prompt']}
{'='*80}
""")
    
    # 组装最终输出
    return f"""
# 智能代码修复提示词生成完成

## 统计信息
- **项目**: {project_name}
- **总问题数**: {total}
- **本批次**: {len(issues)} 个
- **成功生成**: {success_count} 个

## 智能提示词列表
{"".join(results)}

---
由智能提示词生成引擎提供支持
"""

if __name__ == "__main__":
    mcp.run("stdio")

整合要点

  • 错误处理:每个issue独立处理,单个失败不影响其他
  • 元数据追踪:记录文件、行号、问题类型、Token估算
  • 结构化输出:Markdown格式,便于Cursor展示

安装依赖包

在使用MCP服务之前,需要先安装必要的Python依赖包。创建requirements.txt文件:

txt 复制代码
# 智能代码修复MCP服务依赖包

# MCP框架 - 用于构建MCP服务
mcp>=0.9.0

# HTTP客户端 - 用于调用Sonar API
httpx>=0.27.0

# Java AST解析器 - 精确解析Java代码结构(强烈推荐)
# 优势:准确率99%+,不受注释、字符串、Lambda影响
# 注意:这是纯Python库,不需要Java环境
javalang>=0.13.0

安装命令:

bash 复制代码
pip install -r requirements.txt

依赖说明

  • mcp:MCP协议框架,让Python脚本能够被Cursor识别和调用
  • httpx:现代化HTTP客户端,用于调用Sonar API获取扫描结果
  • javalang:纯Python的Java AST解析器,用于精准定位代码结构,无需安装Java环境

配置MCP(让Cursor识别你的服务)

%这里改成你本地的路径% 在 ~/.cursor/mcp.json 中添加:

json 复制代码
{
  "mcpServers": {
    "intelligent-code-fix": {
      "command": "python",
      "args": ["/Users/Desktop/cursor/intelligent-code-fix.py"],
      "env": {}
    }
  }
}

配置说明

  • intelligent-code-fix:MCP服务名称,在Cursor中通过@intelligent-code-fix调用
  • command: Python解释器命令
  • args: Python脚本的绝对路径(请修改为你的实际路径)
  • 修改配置后需要重启Cursor生效

四、使用体验:在Cursor中一键修复

配置好MCP后,在Cursor中的使用非常简单

步骤1:在Cursor聊天窗口调用MCP工具

diff 复制代码
使用 auto-fix-sonar-issues 工具修复Sonar问题

参数:

- project_name: hunter_partner_recycle_core
- cookie: sso_uid=xxx; ticket=xxx; aid=xxx
- base_dir: /Users/xxx/projects/hunter/src/main/java
- branch: feature-3278-163 (可选)
- page_size: 10

步骤2:MCP服务自动处理

系统自动完成:

  1. 调用Sonar API获取问题列表
  2. 为每个问题智能生成提示词
  3. 返回结构化的修复建议

步骤3:AI生成修复后的代码

ini 复制代码
已生成10个问题的修复建议:

Issue 1: ActivityServiceImpl.java 空指针修复
修复后代码:
[完整的修复代码]

Issue 2: FileUtil.java 资源泄露修复
修复后代码:
[完整的修复代码]

步骤4:一键应用修复

直接在Cursor中review并应用修复

改进效果:

  1. 成本降低70% :精准提取上下文,token使用量大幅减少
  2. 准确率提升30%:结构化提示词+示例引导,AI理解更准确
  3. 效率提升3倍:自动化流程,无需人工干预
  4. 质量可控:分层模板确保代码符合团队规范
  5. 易于扩展:新增问题类型只需添加配置

五、写在最后

真正有效的AI辅助开发,不是简单地把代码扔给AI,而是要做好:

  • 问题的精准识别:知道是什么类型的问题
  • 上下文的智能提取:给AI最相关的代码,而不是全部代码
  • 规范的有效注入:让AI知道要遵循什么标准
  • 经验的系统沉淀:用示例库让AI学习最佳实践

这套方案不仅适用于Sonar,也可以推广到其他代码扫描工具(ESLint、Checkstyle等)。核心思想都是:将非结构化的扫描结果转换为结构化的AI提示词

希望这篇文章能给大家带来启发。如果大家也在探索AI辅助开发,欢迎交流!

关于作者,刘雅斌,侠客汇Java开发工程师。 想了解更多转转公司的业务实践,欢迎点击关注下方公众号
转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。 关注公众号「转转技术」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎交流分享~

相关推荐
程序员鱼皮2 小时前
什么是 RESTful API?凭什么能流行 20 多年?
前端·后端·程序员
石头dhf2 小时前
大模型配置
开发语言·python
南科1号2 小时前
Tushare数据来源分析一例
python
质变科技AI就绪数据云2 小时前
AI Data独角兽猎手的12个预测(2026)
人工智能·向量数据库·ai agent
互联网志2 小时前
交通运输行业作为人工智能落地领域,是一个庞大的人工智能应用场景
人工智能·百度
小程故事多_802 小时前
Agent Skills深度解析,让智能体从“会连接”到“会做事”的核心引擎
数据库·人工智能·aigc
API技术员2 小时前
京东API接口:如何高效获取商品详情与SKU信息
python
啊巴矲2 小时前
小白从零开始勇闯人工智能:深度学习初级篇(初识深度学习及环境的配置与安装)
人工智能·深度学习
sort浅忆3 小时前
deeptest执行接口脚本,添加python脚本断言
开发语言·python