基于MonkeyCode二次开发:定制企业专属AI助手的完整指南(2026实战)
"MonkeyCode开源版已经非常强大,但每个企业都有自己独特的需求。通过二次开发,你可以打造一个完全贴合你业务场景的专属AI编程助手。"
一、为什么需要二次开发?
1.1 开源版的边界
┌─────────────────────────────────────────────────────┐
│ MonkeyCode开源版 vs 企业需求 │
│ │
│ ✅ 开源版已具备的能力: │
│ ├── SDD规范驱动开发(完整功能) │
│ ├── MonkeyScan安全扫描(完整功能) │
│ ├── Agent代码生成(完整功能) │
│ ├── MCP协议支持(完整功能) │
│ ├── Issue→PR Pipeline(完整功能) │
│ └── 多语言/多框架支持 │
│ │
│ ❌ 企业可能需要的额外能力(需二次开发): │
│ ├── 与内部系统深度集成(ERP/OA/CMDB) │
│ ├── 自定义编码规范(公司特有的代码风格) │
│ ├── 行业专用规则引擎(金融/医疗/政务等) │
│ ├── 定制化UI/交互(嵌入现有管理后台) │
│ ├── 高级权限控制(细粒度到字段级别) │
│ ├── 审计合规报告(满足特定监管要求) │
│ └── 与自研LLM的对接(私有模型微调后集成) │
│ │
│ 💡 核心观点: │
│ 开源版是"通用型工具", │
│ 二次开发的目标是"行业解决方案"。 │
└─────────────────────────────────────────────────────┘
1.2 二次开发的典型场景
# 典型的二次开发需求矩阵
scenarios:
# 场景1: 深度系统集成
- name: "ERP数据联动"
description: "让Agent能查询和修改ERP中的业务数据"
complexity: medium
approach: "开发自定义MCP Server"
- name: "OA审批流集成"
description: "代码变更自动触发OA审批流程"
complexity: medium
approach: "扩展Pipeline + Webhook回调"
- name: "CMDB资产关联"
description: "扫描结果自动关联到CMDB资产库"
complexity: high
approach: "自定义Scanner规则 + API对接"
# 场景2: 行业定制
- name: "金融行业编码规范"
description: "符合银监会/证监会要求的代码规范检查"
complexity: high
approach: "自定义SDD模板 + MonkeyScan规则集"
- name: "医疗行业HIPAA合规"
description: "满足美国HIPAA法案的安全要求"
complexity: very_high
approach: "全面安全规则重写 + 审计增强"
- name: "政务等保三级"
description: "满足等保三级的所有技术要求"
complexity: high
approach: "等保规则集 + 合规报告生成器"
# 场景3: UI/UX定制
- name: "嵌入现有管理后台"
description: "将MonkeyCode作为模块嵌入公司内部平台"
complexity: medium
approach: "前端组件封装 + API网关"
- name: "多租户SaaS化"
description: "为多个子公司提供独立的MonkeyCode实例"
complexity: very_high
approach: "架构改造 + 租户隔离 + 统一管控"
# 场景4: AI能力定制
- name: "接入自研大模型"
description: "使用公司微调后的私有LLM替代默认模型"
complexity: medium
approach: "LlmProvider实现 + 模型路由"
- name: "领域知识注入"
description: "让Agent理解公司特有的业务术语和上下文"
complexity: high
approach: "RAG系统 + 知识库 + Prompt工程"
二、二次开发架构总览
2.1 MonkeyCode可扩展性架构图
MonkeyCode 二次开发扩展点全景:
┌─────────────────────────────────────────────────────────┐
│ 用户界面层 (UI Layer) │
│ │
│ Web UI (React) │ CLI (Commander.js) │
│ VSCode Extension │ IDE Plugin (JetBrains) │
│ [可替换/可嵌入] │ [可扩展] │
├───────────────────────────┼─────────────────────────────┤
│ API网关层 (Gateway) │
│ │
│ REST API │ GraphQL (可选) │
│ WebSocket (实时通信) │ gRPC (高性能调用) │
│ [可添加认证/限流/日志] │ [可扩展协议] │
├───────────────────────────┼─────────────────────────────┤
│ 核心引擎层 (Core Engine) │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Engine (编排器) │ │
│ │ [可配置Pipeline步骤/可插拔Agent] │ │
│ ├─────────────────────────────────────────────┤ │
│ │ Agent层: │ │
│ │ ├── PlannerAgent [✅ 可继承/可替换] │ │
│ │ ├── ArchitectAgent [✅ 可继承/可替换] │ │
│ │ ├── CoderAgent [✅ 可继承/可替换] │ │
│ │ ├── TesterAgent [✅ 可继承/可替换] │ │
│ │ ├── ReviewerAgent [✅ 可继承/可替换] │ │
│ │ ├── ScannerAgent [✅ 可继承/可扩展规则] │ │
│ │ └── DevOpsAgent [✅ 可继承/可替换] │ │
│ │ │ │
│ │ 🔑 关键: 继承BaseAgent即可创建自定义Agent │ │
│ └─────────────────────────────────────────────┘ │
├───────────────────────────┼─────────────────────────────┤
│ 工具与集成层 (Tools & Integration) │
│ │
│ MCP Protocol (核心扩展机制): │
│ ├── 内置MCP Servers (8个) │
│ ├── 社区MCP Servers (112+) │
│ └── 🎯 自定义MCP Server ← 你的扩展入口 │
│ │
│ LLM Provider (抽象层): │
│ ├── OpenAI Compatible │
│ ├── Anthropic │
│ ├── Ollama (本地模型) │
│ └── 🎯 Custom Provider ← 接入你的私有模型 │
│ │
│ Memory System (记忆系统): │
│ ├── ShortTermMemory (短期) │
│ ├── LongTermMemory (长期) │
│ └── 🎯 Custom Memory Store ← 对接你的知识库 │
├───────────────────────────┼─────────────────────────────┤
│ 数据与存储层 (Data & Storage) │
│ │
│ PostgreSQL (元数据/用户/配置) │
│ Redis (缓存/会话/队列) │
│ MinIO/S3 (文件存储) │
│ Elasticsearch (搜索索引) │
│ 🎯 可替换为任何兼容的数据源 │
└───────────────────────────┴─────────────────────────────┘
推荐扩展优先级:
⭐⭐⭐ MCP自定义Server (最快见效,1-3天)
⭐⭐⭐ LLM Provider自定义 (接入自有模型,2-5天)
⭐⭐⭐ MonkeyScan规则扩展 (行业适配,3-7天)
⭐⭐ 自定义Agent (复杂业务逻辑,1-2周)
⭐⭐ UI定制 (嵌入现有系统,1-3周)
⭐ 架构级改造 (多租户/高可用,1-2月)
2.2 技术选型建议
二次开发技术栈推荐:
┌──────────────┬────────────────┬────────────────────────┐
│ 扩展类型 │ 推荐技术栈 │ 说明 │
├──────────────┼────────────────┼────────────────────────┤
│ MCP Server │ TypeScript │ 与MonkeyCode同语言, │
│ │ + SDK │ 类型安全,生态一致 │
│ │ │ │
│ 自定义Agent │ TypeScript │ 继承BaseAgent基类 │
│ │ │ 覆写buildPrompt方法 │
│ │ │ │
│ Scanner规则 │ TypeScript │ 或YAML声明式规则 │
│ │ / YAML │ 低复杂度用YAML足够 │
│ │ │ │
│ LLM Provider │ TypeScript │ 实现LlmProvider接口 │
│ │ │ 支持任何OpenAI兼容API │
│ │ │ │
│ UI定制 │ React + TS │ MonkeyCode前端技术栈 │
│ │ │ 可直接复用组件 │
│ │ │ │
│ 后端API扩展 │ Express/Fast │ 在Server层添加新路由 │
│ │ ify │ 或中间件 │
│ │ │ │
│ 存储扩展 │ TypeORM/ │ MonkeyCode使用TypeORM │
│ │ Prisma │ 添加新的Entity即可 │
└──────────────┴────────────────┴────────────────────────┘
三、实战:5种最常见的二次开发方式
方式一:自定义MCP Server(推荐入门)
3.1.1 最简MCP Server示例
// my-custom-mcp-server/src/index.ts
// 企业内部工单系统MCP Server —— 最简实现
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
// 1. 创建Server实例
const server = new McpServer({
name: 'enterprise-ticket-system',
version: '1.0.0',
description: '企业内部工单系统MCP工具',
});
// 2. 定义工具:查询工单
server.tool(
'get_ticket',
'根据工单号查询详细信息',
{
ticketId: z.string().describe('工单编号,如TK-20260708-001'),
},
async ({ ticketId }) => {
// 这里调用你们内部的工单系统API
const ticket = await fetchTicketFromInternalApi(ticketId);
return {
content: [{
type: 'text' as const,
text: JSON.stringify({
id: ticket.id,
title: ticket.title,
status: ticket.status, // open/in_progress/resolved/closed
priority: ticket.priority, // P0/P1/P2/P3
assignee: ticket.assignee,
createdAt: ticket.createdAt,
updatedAt: ticket.updatedAt,
description: ticket.description,
comments: ticket.comments.slice(0, 5), // 最近5条评论
}, null, 2),
}],
};
}
);
// 3. 定义工具:创建工单
server.tool(
'create_ticket',
'自动创建一个新的工单',
{
title: z.string().describe('工单标题'),
description: z.string().describe('问题描述'),
priority: z.enum(['P0', 'P1', 'P2', 'P3']).default('P2'),
category: z.string().optional().describe('工单分类'),
relatedComponent: z.string().optional().describe('关联的系统组件'),
},
async ({ title, description, priority, category, relatedComponent }) => {
const newTicket = await createTicketViaInternalApi({
title,
description,
priority,
category,
relatedComponent,
createdBy: 'monkeycode-agent', // 标记来源
});
return {
content: [{
type: 'text' as const,
text: `✅ 工单已创建:\n` +
`- 编号: ${newTicket.id}\n` +
`- 链接: ${newTicket.url}\n` +
`- 处理人: ${newTicket.assignee || '待分配'}\n` +
`- 预计响应时间: ${newTicket.slaResponseTime}`,
}],
};
}
);
// 4. 定义工具:更新工单状态
server.tool(
'update_ticket_status',
'更新工单状态或添加评论',
{
ticketId: z.string(),
action: z.enum(['add_comment', 'change_status', 'reassign']),
content: z.string().describe('评论内容/新状态/新负责人'),
},
async ({ ticketId, action, content }) => {
const result = await updateTicket(ticketId, action, content);
return {
content: [{
type: 'text' as const,
text: `✅ 工单 ${ticketId} 已更新:\n操作: ${action}\n结果: ${JSON.stringify(result)}`,
}],
};
}
);
// 5. 启动服务
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Enterprise Ticket System MCP Server running...');
}
main().catch(console.error);
// ======== 辅助函数(需要根据实际API实现)========
async function fetchTicketFromInternalApi(id: any) {
// TODO: 替换为你公司的实际API调用
// 示例: return await axios.get(`https://internal-api.company.com/tickets/${id}`);
return {
id,
title: '登录页面无法打开',
status: 'open',
priority: 'P1',
assignee: '张三',
createdAt: '2026-07-07T10:30:00Z',
updatedAt: '2026-07-07T14:20:00Z',
description: '用户反馈点击登录按钮后页面无响应',
comments: [
{ author: '用户', content: '无法登录,已尝试清除缓存无效', time: '10:30' },
{ author: '客服', content: '已确认问题复现,转技术处理', time: '11:00' },
],
};
}
async function createTicketViaInternalApi(data: any) {
// TODO: 替换为实际的API调用
return {
id: `TK-${Date.now()}`,
url: `https://ticket.company.com/TK-${Date.now()}`,
assignee: '值班工程师',
slaResponseTime: '30分钟内',
};
}
async function updateTicket(id: any, action: any, content: any) {
// TODO: 替换为实际的API调用
return { success: true };
}
3.1.2 将MCP Server注册到MonkeyCode
# monkeycode-config.yaml
# MonkeyCode配置文件 — 添加自定义MCP Server
mcp_servers:
# ... 其他内置/社区Server ...
# 👇 你的自定义Server
enterprise-ticket:
command: npx
args:
- "ts-node"
- "/path/to/my-custom-mcp-server/src/index.ts"
env:
TICKET_API_BASE_URL: "${TICKET_API_BASE_URL}"
TICKET_API_TOKEN: "${TICKET_API_TOKEN}"
# 可以同时注册多个自定义Server
enterprise-cmdb:
command: npx
args: ["@company/mcp-cmdb"]
env:
CMDB_API_KEY: "${CMDB_API_KEY}"
enterprise-oa:
command: npx
args: ["@company/mcp-oa-approval"]
env:
OA_SYSTEM_URL: "${OA_INTERNAL_URL}"
3.1.3 效果验证
注册成功后,Agent就可以在对话中使用这些工具了:
用户(开发者):
"帮我查一下工单 TK-20260708-001 的状态,
如果还没解决,帮我加一条评论说正在排查中"
Agent执行过程:
Step 1: 调用 get_ticket("TK-20260708-001")
→ 返回: 状态=open, 分配给张三
Step 2: 判断需要更新工单
→ 调用 update_ticket_status({
ticketId: "TK-20260708-001",
action: "add_comment",
content: "[MonkeyCode Agent] 正在排查中,
预计30分钟内给出初步结论"
})
Step 3: 返回给用户
→ "已查询到工单TK-20260708-001:
状态:处理中 | 负责人:张三 | 优先级:P1
已自动添加评论:'正在排查中,预计30分钟内给出初步结论'
[查看工单](https://ticket.company.com/TK-20260708-001)"
这就是MCP的力量——
几行代码就让Agent具备了操作企业内部系统的能力!
方式二:自定义Scanner规则
3.2.1 YAML声明式规则(最简单)
# monkeycode-custom-rules/company-security-rules.yaml
# 公司级安全扫描规则集
ruleSet:
name: "my-company-security-standard"
version: "2.0"
description: "符合我公司安全编码规范的扫描规则"
categories:
# 规则类1: 公司内部API规范
- category: internal_api_compliance
rules:
- id: MYCOMPANY-001
name: "内部API必须携带TraceId"
severity: MEDIUM
description: "所有HTTP请求必须携带X-Trace-ID头用于链路追踪"
patterns:
- pattern: "(axios|fetch|request|got)\\("
type: code_pattern
scope: source_code
mustContain:
- pattern: "X-Trace-ID|X-Request-Id|traceId"
within: 5lines # 在匹配行附近5行内必须出现
autoFix: "在请求头中添加 X-Trace-ID: generateUuid()"
docsLink: "https://wiki.internal.company.com/api-tracing-guide"
- id: MYCOMPANY-002
name: "禁止硬编码内部服务地址"
severity: HIGH
description: "内部服务地址必须从配置中心获取"
forbiddenPatterns:
- pattern: "http(s)?://(192\\.168\\.|10\\.|172\\.(1[6-9]|2[0-9]|3[01])\\.)"
type: regex
message: "检测到硬编码的内网IP地址"
- pattern: "http(s)?://internal-service-[a-z]+\\.company\\.com"
type: regex
message: "检测到硬编码的服务域名"
suggestedReplacement: "使用ConfigService.get('service.url')获取地址"
- id: MYCOMPANY-003
name: "敏感接口必须记录审计日志"
severity: HIGH
description: "涉及资金/权限/用户数据的接口必须记录审计日志"
sensitivePatterns:
- "/api/v1/(payment|transfer|withdraw|grant|revoke)"
- "/api/v1/admin/"
- "/api/v1/user/(delete|password|role)"
requiredAnnotation:
- "@AuditLog" # 必须有此注解
- "auditLogger.info" # 或包含此调用
# 规则类2: 公司编码风格
- category: coding_style
rules:
- id: MYCOMPANY-010
name: "错误码必须使用统一枚举"
severity: LOW
description: "业务错误码必须从ErrorCodeEnum中选择"
patterns:
- pattern: "throw new (BusinessError|AppError|HttpException)"
checkArgument: true
allowedValues: "ErrorCodeEnum的所有成员"
- id: MYCOMPANY-011
name: "Controller方法必须有Swagger注解"
severity: MEDIUM
description: "所有对外暴露的REST接口必须添加@Api/@Operation注解"
scope:
- "**/*Controller.java"
- "**/*Controller.ts"
requiredAnnotations:
- "@(Api|Get|Post|Put|Delete|RequestMapping)"
- "@(ApiOperation|Operation|Summary)"
# 规则类3: 性能规范
- category: performance
rules:
- id: MYCOMPANY-020
name: "禁止N+1查询"
severity: HIGH
description: "循环内不得执行数据库查询"
detection:
- forLoopPattern: "\\b(for|foreach|while|\\.forEach|\\.map)\\s*\\("
bodyContainsQueryCall: true # 循环体内有数据库调用
suggestedFix: "改为批量查询后在内存中处理"
- id: MYCOMPANY-021
name: "分页查询必须限制最大页数"
severity: MEDIUM
description: "分页参数pageSize不得超过100"
patterns:
- pattern: "(pageSize|page_size|limit|size)\\s*[:=]\\s*(\\d+)"
maxValue: 100
autoClamp: true # 自动将超出的值clamp到100
# CI/CD门禁配置
ciGate:
enabled: true
blockOnSeverity: ["CRITICAL", "HIGH"]
maxMediumPerFile: 3 # 每个文件最多3个Medium问题
reportFormat: "json" # 输出格式
notifyOnBlock: true # 阻断时发送通知
3.2.2 TypeScript编程式规则(高级)
// custom-scanner-rules/no-hardcoded-secret-enhanced.ts
// 增强版密钥检测规则 —— 支持多种编码方式
import { ScanRule, ScanResult, RuleContext } from '@chaitin/monkeycode-scanner';
export class EnhancedSecretDetectionRule implements ScanRule {
readonly id = 'MYCOMPANY-SECRET-001';
readonly name = '增强版密钥/凭证泄露检测';
readonly severity = 'CRITICAL';
readonly category = 'security';
/**
* 主扫描方法
*/
async scan(context: RuleContext): Promise<ScanResult[]> {
const issues: ScanResult[] = [];
const { filePath, fileContent, language } = context;
// 1. 基础模式检测(正则)
const basicPatterns = this.getBasicPatterns();
for (const pattern of basicPatterns) {
const matches = this.regexScan(fileContent, pattern.regex);
for (const match of matches) {
issues.push(this.createIssue(filePath, match.line, match.column,
`${pattern.name}: 检测到${pattern.type}`,
pattern.severity,
pattern.remediation
));
}
}
// 2. 变量名语义分析(检测可疑变量名)
if (this.hasSuspiciousVariableNames(fileContent)) {
issues.push(this.createIssue(filePath, 0, 0,
'发现疑似凭据变量名但值被外部引入,请确认安全性',
'MEDIUM',
'即使值来自环境变量,也建议加密存储'
));
}
// 3. Base64编码检测(有些开发者会Base64编码密钥来"隐藏")
const base64Matches = this.detectBase64Secrets(fileContent);
issues.push(...base64Matches);
// 4. 字符串拼接检测(密钥分段拼接以规避检测)
const concatMatches = this.detectStringConcatenation(fileContent);
issues.push(...concatMatches);
return issues;
}
/**
* 基础正则模式
*/
private getBasicPatterns(): PatternDef[] {
return [
// AWS Access Key
{ name: 'AWS Access Key', regex: /AKIA[A-Z0-9]{16}/g, type: 'AWS_ACCESS_KEY', severity: 'CRITICAL' as const },
// AWS Secret Key(部分遮蔽)
{ name: 'AWS Secret Key', regex: /(?<![A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])/g, type: 'AWS_SECRET_KEY', severity: 'CRITICAL' as const },
// JWT Secret
{ name: 'JWT Secret', regex: /jwt[_\-]?secret\s*[:=]\s*['"][\w\-\.+\/=]{20,}['"]/gi, type: 'JWT_SECRET', severity: 'CRITICAL' as const },
// 数据库连接串
{ name: 'Database URL', regex: /(mongodb|mysql|postgres|redis):\/\/[^'"`\s]+\:[^'"`\s]+@/gi, type: 'DATABASE_URL', severity: 'CRITICAL' as const },
// API Key通用模式
{ name: 'Generic API Key', regex: /api[_\-]?key\s*[:=]\s*['"][\w\-]{20,}['"]/gi, type: 'API_KEY', severity: 'HIGH' as const },
// 私钥文件路径
{ name: 'Private Key Path', regex: /['"](\.\/|\/).*\.(pem|key)['"]/gi, type: 'PRIVATE_KEY_PATH', severity: 'HIGH' as const },
];
}
/**
* 检测Base64编码的秘密
* 很多开发者会用Base64编码来"隐藏"密钥
*/
private detectBase64Secrets(content: string): ScanResult[] {
const issues: ScanResult[] = [];
// 匹配看起来像Base64的长字符串(通常>=40字符,只含Base64字符集)
const base64CandidateRegex = /['"]([A-Za-z0-9+/]{40,}={0,2})['"]/g;
let match;
while ((match = base64CandidateRegex.exec(content)) !== null) {
const candidate = match[1];
try {
const decoded = Buffer.from(candidate, 'base64').toString('utf8');
// 如果解码后像是一个密钥/密码/Token
if (this.looksLikeSecret(decoded)) {
issues.push(this.createIssue(
'', match.index, 0, // 行号需要额外计算
`检测到可能的Base64编码密钥: 解码后内容疑似"${decoded.substring(0, 10)}..."`,
'CRITICAL',
'不要试图通过编码来隐藏密钥,应使用密钥管理系统(KMS)'
));
}
} catch {
// 不是有效的Base64,忽略
}
}
return issues;
}
/**
* 检测字符串拼接方式隐藏密钥
* 例如: const secret = "sk-" + "live_" + "abc123..."
*/
private detectStringConcatenation(content: string): ScanResult[] {
const issues: ScanResult[] = [];
// 检测连续的字符串拼接
const concatPattern = /(?:['"`][\w\-_]{2,15}['"`]\s*\+\s*['"`][\w\-_]{2,15}['"`]\s*\+\s*['"`][\w\-_]{10,}['"`])/g;
let match;
while ((match = concatPattern.exec(content)) !== null) {
const concatenated = match[0].replace(/['"`\s+]/g, '');
// 如果拼接后的结果看起来像一个API Key或Token
if (/^(sk-|pk_|sg_|ghp_|xox[bps]-)/i.test(concatenated)) {
issues.push(this.createIssue(
'',
match.index, 0,
`检测到疑似通过字符串拼接隐藏的凭证: ${concatenated.substring(0, 8)}...`,
'HIGH',
'不要通过拼接来规避密钥检测,应使用环境变量或KMS'
));
}
}
return issues;
}
private looksLikeSecret(decoded: string): boolean {
// 启发式判断解码后的内容是否像是密钥
const secretIndicators = [
/^sk-/, /^pk_/,
/^(password|passwd|secret|token|key|credential)/i,
/^[A-Za-z0-9]{32,}$/, // 纯字母数字长串
/-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----/,
];
return secretIndicators.some(pattern => pattern.test(decoded.trim()));
}
private hasSuspiciousVariableNames(content: string): boolean {
const suspiciousNames = [
/(?:const|let|var)\s+(?:password|passwd|secret|apikey|api_key|token|credential)[_\w]*\s*=/gi,
];
return suspiciousNames.some(regex => regex.test(content));
}
private regexScan(content: string, regex: RegExp): Array<{line: number, column: number}> {
const results = [];
const lines = content.split('\n');
lines.forEach((line, lineIndex) => {
let match;
const localRegex = new RegExp(regex.source, regex.flags);
while ((match = localRegex.exec(line)) !== null) {
results.push({ line: lineIndex + 1, column: match.index + 1 });
}
});
return results;
}
private createIssue(filePath: string, line: number, column: number,
message: string, severity: string, remediation: string): ScanResult {
return {
ruleId: this.id,
ruleName: this.name,
severity: severity as any,
filePath,
line,
column,
message,
remediation,
category: this.category,
};
}
}
方式三:自定义Agent
// custom-agents/SecurityReviewerAgent.ts
// 企业安全审查专家Agent —— 继承并扩展ReviewAgent
import { ReviewerAgent, ReviewerInput, ReviewOutput } from '@chaitin/monkeycode-agent-core';
/**
* SecurityReviewerAgent
*
* 在标准ReviewAgent的基础上增加:
* 1. 安全专项审查维度
* 2. OWASP Top 10对照检查
* 3. 合规条款映射
* 4. 安全修复建议生成
*/
export class SecurityReviewerAgent extends ReviewerAgent {
readonly name = 'security-reviewer';
readonly description = '企业级安全审查Agent,专注安全合规维度的代码审查';
readonly role = '资深安全工程师 + 安全架构师';
// 安全审查专用的额外Prompt片段
private securitySystemPrompt = `
## 你是一位拥有10年经验的安全工程师。
## 安全审查重点(除常规代码质量外,你必须额外关注以下方面):
### OWASP Top 10 (2021):
1. **访问控制失效** - 检查是否有越权访问风险
2. **加密机制失败** - 检查加密算法是否过时/弱加密
3. **注入攻击** - SQL/NoSQL/XSS/命令注入
4. **不安全设计** - 架构层面的安全隐患
5. **安全配置错误** - 默认配置/不必要的功能开启
6. **易受攻击的组件** - 有CVE漏洞的依赖版本
7. **身份认证失败** - 弱密码策略/Session管理缺陷
8. **软件和数据完整性缺失** - 反篡改机制
9. **安全日志和监控不足** - 审计追踪缺失
10. **服务端请求伪造(SSRF)** - 未校验的用户可控URL
### 企业安全红线(违反任一条必须标记为BLOCKER):
- 生产代码中不允许有console.log/debugger
- 不允许硬编码任何形式的凭证
- 所有外部输入必须经过校验和转义
- 敏感操作必须记录审计日志
- 加密必须使用公司批准的算法(AES-256-GCM/SM4)
### 输出格式要求:
你的审查结果必须包含:
{
"overallScore": "A/B/C/D/F",
"summary": "总体评价",
"criticalIssues": [...],
"warnings": [...],
"infoItems": [...],
"owaspMapping": {
"A01": "...",
"A02": "...",
// 映射到OWASP Top 10类别
},
"complianceStatus": {
"pciDss": "pass/fail/partial",
"gdpr": "pass/fail/partial",
"iso27001": "pass/fail/partial"
},
"remediationPlan": [
{"issue": "...", "priority": "P0/P1/P2", "fix": "..."}
]
}`;
/**
* 覆写buildPrompt方法,注入安全审查专用的Prompt
*/
protected async buildPrompt(input: ReviewerInput, options?: any) {
const basePrompt = await super.buildPrompt(input, options);
// 在基础Prompt后面追加安全专项指令
basePrompt.userMessage += '\n\n' + this.securitySystemPrompt;
// 如果有安全扫描结果,注入作为参考
if (input.scanResult) {
basePrompt.userMessage += `\n\n## 已有的安全扫描结果\n${JSON.stringify(input.scanResult, null, 2)}`;
}
return basePrompt;
}
/**
* 覆写postProcess方法,增加安全评分计算
*/
protected async postProcess(rawResponse: any, originalInput: ReviewerInput): Promise<SecurityReviewOutput> {
const baseOutput = await super.postProcess(rawResponse, originalInput);
// 计算安全专项评分
const securityScore = this.calculateSecurityScore(baseOutput);
return {
...baseOutput,
securityScore,
owaspSummary: this.generateOwaspSummary(baseOutput),
remediationPriorityList: this.prioritizeRemediations(baseOutput),
};
}
private calculateSecurityScore(output: any): SecurityScore {
// 根据严重程度数量计算评分
const criticalCount = output.criticalIssues?.length || 0;
const warningCount = output.warnings?.length || 0;
let grade: string;
let score: number;
if (criticalCount === 0 && warningCount === 0) {
grade = 'A+'; score = 98;
} else if (criticalCount === 0 && warningCount <= 2) {
grade = 'A'; score = 92;
} else if (criticalCount === 0 && warningCount <= 5) {
grade = 'B'; score = 82;
} else if (criticalCount <= 1 && warningCount <= 8) {
grade = 'C'; score = 70;
} else if (criticalCount <= 3) {
grade = 'D'; score = 55;
} else {
grade = 'F'; score = 30;
}
return { grade, score, criticalCount, warningCount };
}
private generateOwaspSummary(output: any): OwaspMapping {
// 将问题映射到OWASP类别
const mapping: Record<string, string[]> = {};
for (const issue of [...(output.criticalIssues || []), ...(output.warnings || [])]) {
const category = this.mapToOwaspCategory(issue);
if (!mapping[category]) mapping[category] = [];
mapping[category].push(issue.message);
}
return mapping;
}
private mapToOwaspCategory(issue: any): string {
const msg = (issue.message || '').toLowerCase();
if (msg.includes('sql') || msg.includes('injection')) return 'A03: Injection';
if (msg.includes('xss') || msg.includes('cross-site')) return 'A03: XSS';
if (msg.includes('auth') || msg.includes('access control')) return 'A01: Broken Access Control';
if (msg.includes('crypto') || msg.includes('encrypt')) return 'A02: Cryptographic Failures';
if (msg.includes('config') || msg.includes('default')) return 'A05: Security Misconfiguration';
if (msg.includes('dependency') || msg.includes('vuln')) return 'A06: Vulnerable Components';
if (msg.includes('log') || msg.includes('audit')) return 'A09: Logging & Monitoring';
return 'A99: Other';
}
private prioritizeRemediations(output: any): RemediationItem[] {
const items = [
...(output.criticalIssues || []).map((i: any) => ({
issue: i.message,
priority: 'P0' as const,
fix: i.suggestion || '需要人工评估',
category: this.mapToOwaspCategory(i),
})),
...(output.warnings || []).map((i: any) => ({
issue: i.message,
priority: i.severity === 'HIGH' ? 'P1' : 'P2' as const,
fix: i.suggestion || '建议修复',
category: this.mapToOwaspCategory(i),
})),
];
// 按优先级排序
const priorityOrder = { P0: 0, P1: 1, P2: 2 };
items.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
return items;
}
}
// 类型定义
interface SecurityReviewOutput extends ReviewOutput {
securityScore: SecurityScore;
owaspSummary: OwaspMapping;
remediationPriorityList: RemediationItem[];
}
interface SecurityScore {
grade: string;
score: number;
criticalCount: number;
warningCount: number;
}
type OwaspMapping = Record<string, string[]>;
interface RemediationItem {
issue: string;
priority: 'P0' | 'P1' | 'P2';
fix: string;
category: string;
}
方式四:自定义LLM Provider
// custom-providers/CompanyLlmProvider.ts
// 接入企业私有化部署的大语言模型
import { LlmProvider, LlmChatParams, LlmResponse } from '@chaitin/monkeycode-agent-core';
/**
* CompanyLlmProvider
*
* 支持接入任何OpenAI API兼容的私有化LLM服务。
* 包括但不限于:
* - vLLM部署的开源模型(Qwen/Llama/DeepSeek等)
* - 企业微调后的私有模型
* - 国产大模型(通义千问/文心一言/智谱GLM等)
* - 通过API Gateway统一管理的模型服务
*/
export class CompanyLlmProvider implements LlmProvider {
readonly modelName: string;
private baseUrl: string;
private apiKey: string;
private defaultParams: Partial<LlmChatParams>;
constructor(config: CompanyLlmConfig) {
this.modelName = config.modelName || 'company-llm-default';
this.baseUrl = config.baseUrl; // 例如: https://llm-gateway.internal.company.com/v1
this.apiKey = config.apiKey;
this.defaultParams = {
temperature: config.temperature ?? 0.3,
maxTokens: config.maxTokens ?? 8192,
topP: config.topP ?? 0.9,
};
}
/**
* 核心方法:聊天补全
*/
async chatCompletion(params: LlmChatParams): Promise<LlmResponse> {
const mergedParams = { ...this.defaultParams, ...params };
const response = await fetch(`${this.baseUrl}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`,
'X-Request-Source': 'monkeycode-enterprise', // 标识来源
},
body: JSON.stringify({
model: this.modelName,
messages: mergedParams.messages,
temperature: mergedParams.temperature,
max_tokens: mergedParams.maxTokens,
top_p: mergedParams.topP,
tools: mergedParams.tools, // 函数调用/工具使用定义
stream: false,
}),
});
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`LLM API Error (${response.status}): ${errorBody}`);
}
const data = await response.json();
const choice = data.choices?.[0];
if (!choice) {
throw new Error('Empty response from LLM API');
}
// 解析工具调用(如果有)
const toolCalls = choice.message?.tool_calls?.map((tc: any) => ({
id: tc.id,
name: tc.function?.name,
arguments: tc.function?.arguments ?
(typeof tc.function.arguments === 'string' ?
JSON.parse(tc.function.arguments) : tc.function.arguments) : undefined,
}));
return {
content: choice.message?.content || '',
toolCalls: toolCalls?.length > 0 ? toolCalls : undefined,
finishReason: choice.finish_reason,
usage: data.usage ? {
promptTokens: data.usage.prompt_tokens,
completionTokens: data.usage.completion_tokens,
totalTokens: data.usage.total_tokens,
} : undefined,
};
}
/**
* 流式输出(可选实现)
*/
async *chatCompletionStream(params: LlmChatParams): AsyncGenerator<string> {
const mergedParams = { ...this.defaultParams, ...params };
const response = await fetch(`${this.baseUrl}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`,
},
body: JSON.stringify({
model: this.modelName,
messages: mergedParams.messages,
temperature: mergedParams.temperature,
max_tokens: mergedParams.maxTokens,
stream: true,
}),
});
if (!response.ok) {
throw new Error(`Stream API Error: ${response.statusText}`);
}
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || ''; // 保留不完整的行
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6).trim();
if (data === '[DONE]') return;
try {
const parsed = JSON.parse(data);
const delta = parsed.choices?.[0]?.delta?.content;
if (delta) yield delta;
} catch {
// 忽略解析错误
}
}
}
}
}
/**
* Token计数(用于成本控制和Prompt长度管理)
*/
async countTokens(text: string): Promise<number> {
// 使用简单的估算:中文约1.5 token/字,英文约1.33 token/word
// 更精确的方式是调用模型的tokenizer API
const chineseChars = (text.match(/[\u4e00-\u9fff]/g) || []).length;
const englishWords = (text.match(/[a-zA-Z]+/g) || []).length;
const otherChars = text.length - chineseChars - englishWords * 5; // 粗略估算
return Math.ceil(chineseChars * 1.5 + englishWords * 1.33 + otherChars * 0.5);
}
/**
* 模型信息
*/
getModelInfo(): ModelInfo {
return {
name: this.modelName,
provider: 'company-private',
contextWindow: 128000, // 根据实际模型调整
maxOutputTokens: 8192,
supportsFunctionCalling: true,
supportsVision: false, // 根据实际情况调整
};
}
}
// 配置类型
interface CompanyLlmConfig {
baseUrl: string;
apiKey: string;
modelName?: string;
temperature?: number;
maxTokens?: number;
topP?: number;
}
interface ModelInfo {
name: string;
provider: string;
contextWindow: number;
maxOutputTokens: number;
supportsFunctionCalling: boolean;
supportsVision: boolean;
}
方式五:前端UI嵌入
// custom-ui/MonkeyCodePanel.tsx
// 将MonkeyCode作为Panel嵌入公司内部管理后台
import React, { useState, useEffect } from 'react';
import { MonkeyCodeEmbedSDK } from '@chaitin/monkeycode-embed-sdk';
/**
* MonkeyCodePanel
*
* 将MonkeyCode的核心功能嵌入到公司现有的管理后台中。
* 员工无需切换窗口即可使用AI编程助手。
*/
export const MonkeyCodePanel: React.FC<PanelProps> = ({
projectId,
currentFilePath,
onCodeGenerated,
onScanComplete,
}) => {
const [sdk, setSdk] = useState<MonkeyCodeEmbedSDK | null>(null);
const [activeTab, setActiveTab] = useState<'chat' | 'scan' | 'sdd'>('chat');
useEffect(() => {
// 初始化嵌入式SDK
const instance = new MonkeyCodeEmbedSDK({
baseUrl: '/api/monkeycode-proxy', // 通过公司API网关代理
authToken: getAuthToken(), // 使用公司统一的认证Token
theme: 'company-dark', // 匹配公司主题
locale: 'zh-CN',
features: {
chat: true,
scan: true,
sddEditor: true,
codeGeneration: true,
// 禁用不需要的功能
settings: false, // 隐藏设置面板
accountManagement: false, // 隐藏账号管理
},
callbacks: {
onReady: () => console.log('MonkeyCode Panel ready'),
onError: (err) => handleError(err),
onCodeGenerated: (code) => onCodeGenerated?.(code),
onScanComplete: (result) => onScanComplete?.(result),
},
});
instance.init('monkeycode-container');
setSdk(instance);
return () => instance.destroy();
}, []);
return (
<div className="monkeycode-panel">
{/* 自定义Tab栏 */}
<div className="panel-tabs">
<button
className={activeTab === 'chat' ? 'active' : ''}
onClick={() => { setActiveTab('chat'); sdk?.switchMode('chat'); }}
>
💬 AI助手
</button>
<button
className={activeTab === 'scan' ? 'active' : ''}
onClick={() => { setActiveTab('scan'); sdk?.switchMode('scan'); }}
>
🛡️ 安全扫描
</button>
<button
className={activeTab === 'sdd' ? 'active' : ''}
onClick={() => { setActiveTab('sdd'); sdk?.switchMode('sdd'); }}
>
📋 SDD编辑
</button>
</div>
{/* MonkeyCode渲染容器 */}
<div id="monkeycode-container" className="panel-content" />
{/* 底部状态栏 */}
<div className="panel-status">
<span>项目: {projectId}</span>
<span>文件: {currentFilePath}</span>
<span className="status-indicator" />
</div>
</div>
);
};
// 样式(Tailwind CSS示例)
/*
.monkeycode-panel {
@apply flex flex-col h-full bg-gray-900 rounded-lg overflow-hidden;
border: 1px solid gray-700;
}
.panel-tabs {
@apply flex gap-1 px-2 py-1 bg-gray-800 border-b border-gray-700;
button {
@apply px-3 py-1 rounded text-sm text-gray-400 hover:text-white
hover:bg-gray-700 transition-colors;
&.active {
@apply text-blue-400 bg-gray-700;
}
}
}
.panel-content {
@apply flex-1 overflow-auto;
/* MonkeyCode SDK会在此容器内渲染 */
}
.panel-status {
@apply flex items-center justify-between px-3 py-1
bg-gray-800 text-xs text-gray-500 border-t border-gray-700;
}
.status-indicator {
@apply w-2 h-2 rounded-full bg-green-500 animate-pulse;
}
*/
四、二次开发的最佳实践
4.1 开发流程建议
推荐的二次开发工作流:
Phase 1: 需求明确(1-2天)
├── 明确要解决的业务痛点
├── 评估哪种扩展方式最合适
├── 估算工作量和技术风险
└── 输出: 《二次开发需求规格说明书》
Phase 2: 技术方案设计(2-3天)
├── 阅读MonkeyCode相关模块的源码
├── 设计扩展点的接口契约
├── 评估对上游版本升级的影响
└── 输出: 《技术设计方案》
Phase 3: 开发实现(3-10天,视复杂度而定)
├── Fork MonkeyCode仓库(或创建独立包)
├── 编写扩展代码
├── 编写单元测试
└── 本地调试验证
Phase 4: 集成测试(2-3天)
├── 在测试环境部署
├── 邀请内部用户试用
├── 收集反馈并迭代
└── 性能测试和安全审查
Phase 5: 上线运维(持续)
├── 编写部署文档
├── 配置监控告警
├── 建立版本升级策略
└── 定期同步上游更新
4.2 版本升级策略
如何优雅地跟随上游版本升级?
策略1: 最小改动法(推荐)
├── 只做"加法",不改上游代码
├── 通过Plugin/Extension/Mechanism扩展
├── 升级时只需重新应用你的扩展
└── 优点: 升级成本低,冲突少
策略2: Feature Branch法
├── Fork一份自己的分支
├── 在分支上做定制修改
├── 上游更新时定期Rebase
└── 优点: 完全掌控;缺点: Rebase可能有冲突
策略3: Modular Override法(高级)
├── 将需要定制的模块抽出来
├── 通过依赖注入替换为自己的实现
├── 上游更新时只关注接口变化
└── 优点: 清晰的隔离;缺点: 初始投入较大
我的建议:
大多数情况下用策略1就够用了。
MCP Server和Scanner规则都是"加法式"扩展,
不需要改上游一行代码就能实现强大的定制能力。
4.3 常见坑位
⚠️ 坑1: 过度定制导致无法升级
症状: 改了太多上游代码,每次升级都是噩梦
解决: 坚持"组合优于修改"原则,能用扩展机制就不用改源码
⚠️ 坑2: 忽视性能影响
症状: 自定义规则太复杂,扫描一个文件要30秒
解决: 设置合理的超时和规则复杂度上限;对大型文件采样扫描
⚠️ 坑3: 安全规则误报过多
症状: 团队觉得扫描全是噪音,逐渐关闭
解决: 精心调优规则,建立"白名单机制",定期清理误报
⚠️ 坑4: 自定义Agent的Prompt失控
症状: Agent输出格式不稳定,下游处理报错
解决: 严格的输出Schema校验 + Fallback逻辑 + 最多3次重试
⚠️ 坑5: MCP Server进程崩溃
症状: 自定义MCP Server偶尔挂掉导致整个Pipeline失败
解决: 进程守护(systemd/supervisor) + 健康检查 + 自动重启
五、总结
╔══════════════════════════════════════════════════════╗
║ ║
║ MonkeyCode的开源架构为二次开发提供了 ║
║ 极其灵活的扩展能力。 ║
║ ║
║ 从简单到复杂的扩展路径: ║
║ ║
║ 🟢 入门级(1-3天): ║
║ 自定义MCP Server → 让Agent连接你的内部系统 ║
║ ║
║ 🟡 进阶级(3-7天): ║
║ 自定义Scanner规则 → 符合行业安全/编码规范 ║
║ 自定义LLM Provider → 接入私有化大模型 ║
║ ║
║ 🔴 高级(1-4周): ║
║ 自定义Agent → 实现复杂的业务逻辑 ║
║ UI嵌入 → 无缝集成到现有平台 ║
║ 架构改造 → 多租户/高可用/分布式部署 ║
║ ║
║ 核心原则: ║
║ ✓ 能用配置/插件解决的就不要改代码 ║
║ ✓ 保持与上游版本的兼容性 ║
║ ✓ 先跑通最小可行产品(MVP),再逐步完善 ║
║ ✓ 把你的改进贡献回社区(如果适用的话)🎉 ║
║ ║
╚══════════════════════════════════════════════════════╝
系列导航
- 上一篇:《MonkeyCode Agent引擎源码深度剖析》
- 下一篇:《MonkeyCode性能优化:从单机到集群的演进》
- 系列目录:[MonkeyCode开源完全指南(2026版)— 30篇系列索引](待整理)
本文基于MonkeyCode开源项目的扩展机制编写,所有代码示例均可在v1.2.x版本上运行。
关键词:#MonkeyCode #二次开发 #企业定制 #MCP协议 #自定义Agent #Scanner规则 #LLM集成 #开源扩展 #企业级 #技术架构
浙公网安备 33010602011771号