| 协议 | 用途 | 状态 |
|---|---|---|
| MCP (Model Context Protocol) | AI Agent ↔ 工具/数据源 | Kiro CLI 原生支持,主力协议 |
| A2A (Agent2Agent Protocol) | Agent ↔ Agent 协作 | Linux Foundation 维护,v1.0 已发布 |
| 2025.8 已合并入 A2A,不再独立存在 |
建议: 项目内 AI 助手核心是 Kiro CLI + 自定义 MCP Server。如果后续需要多 Agent 协作再引入 A2A。
| 项目 | 说明 |
|---|---|
| 框架 | Vue 3 + TypeScript + Vite |
| UI 库 | Naive UI |
| 状态管理 | Pinia(自定义 defineSetupStore 模板) |
| 样式 | UnoCSS (Tailwind 兼容) |
| 图表 | @antv/g2 |
| HTTP | 自定义 FetchClient(支持拦截器、download、blob) |
| 代码编辑器 | Monaco Editor |
前端目录结构:
src/
├── composable/ # 组合式函数
├── components/ # 公共组件
├── event/ # 事件系统
├── injection/ # 依赖注入
├── router/ # 路由配置
├── service/ # API 层(按模块分目录:feedback/, siteInfoManage/, simCardManage/ 等)
├── stores/ # Pinia Store(modules/data/, modules/views/)
├── theme/ # 主题配置
├── types/ # 全局类型
├── utils/ # 工具函数
└── views/ # 页面视图(按功能模块分目录)
├── feedback/
├── site-info-manage/
├── sites-monitor/
├── sim-card-manage/
├── batch-download/
└── ...
| 项目 | 说明 |
|---|---|
| 框架 | Go + Fiber v2 |
| 数据库 | MongoDB + PostgreSQL + MySQL |
| 缓存 | Redis(自定义 cache 层) |
| 对象存储 | MinIO |
| 任务队列 | Asynq (Redis-based) |
| 外部 API | FranklinWH Energy API、Plane V2 Web API、Acceleronix API、SmartHub API |
| 认证 | Casdoor JWT |
| MQTT | FWH Pro MQTT |
后端目录结构:
service/
├── main.go # 入口(初始化所有依赖 + 启动 HTTP 服务)
├── config/ # 配置加载(Viper,从 .env 读取)
├── controllers/ # HTTP 路由处理器(Fiber Handler)
├── services/ # 业务逻辑层
├── repositories/ # 数据访问层
│ ├── mongo/ # MongoDB 操作
│ ├── redis/ # Redis 操作
│ ├── pgsql/ # PostgreSQL 操作
│ ├── mysql/ # MySQL 操作
│ ├── api/ # 外部 API 客户端
│ │ ├── fwh_pro/ # FranklinWH Energy/OSS API
│ │ ├── plane_v1/ # Plane V1 API(API Key 认证)
│ │ ├── plane_v2/ # Plane V2 Web API(Session 认证)
│ │ ├── acceleronix/ # Acceleronix SIM 卡 API
│ │ └── smartHub/ # SmartHub SIM 卡 API
│ ├── mqtt/ # MQTT 通信
│ └── events/ # 事件通信(xLog TCP)
├── routes/ # 路由注册
├── middleware/ # 中间件(认证等)
├── domain/ # 领域模型
├── dto/ # 数据传输对象
├── database/ # 数据库连接初始化
├── cache/ # Redis 缓存封装
├── task/ # 任务队列(Asynq)
├── scheduler/ # 定时任务
├── jobs/ # 后台作业
└── utils/ # 工具包
HTTP Request
│
▼
┌──────────────┐
│ controllers/ │ 解析参数、校验、调用 service、返回 JSON
└──────┬───────┘
│
▼
┌──────────────┐
│ services/ │ 业务逻辑编排、数据转换、调用多个 repository
└──────┬───────┘
│
▼
┌──────────────┐
│repositories/ │ 数据库 CRUD、外部 API 调用、缓存操作
└──────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 开发者终端 │
│ Kiro CLI │
│ (AI Agent / Host) │
└────────────┬────────────────────────────────┬────────────────────┘
│ MCP (stdio) │ MCP (stdio)
▼ ▼
┌────────────────────────┐ ┌──────────────────────────────┐
│ 内部工具 MCP Server │ │ 外部服务 MCP Server │
│ (Go) │ │ (社区开源) │
├────────────────────────┤ ├──────────────────────────────┤
│• query_site_info │ │• AWS Docs 搜索 │
│• search_sites │ │• Brave 网络搜索 │
│• query_sim_card │ └──────────────────────────────┘
│• query_power_flow │
│• create_plane_issue │
│• query_plane_issues │
│• query_alarm_stats │
└────────────┬───────────┘
│ 直接 import 复用
▼
┌────────────────────────────────────────────────────────────────┐
│ 现有后端 service/ 代码 │
├────────────────────────────────────────────────────────────────┤
│ services/ │ repositories/ │ domain/dto │
│ (业务逻辑) │ (数据访问+外部API) │ (数据模型) │
└─────────┬───────────┴──────────┬─────────────┴─────────────────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────────────────────────────────────┐
│ MongoDB │ │ 外部系统 │
│ PostgreSQL │ │ • FranklinWH Energy API │
│ Redis │ │ • Plane V2 Web API │
│ MinIO │ │ • Acceleronix / SmartHub API │
└──────────────┘ └──────────────────────────────────────────────┘
核心原则:MCP Tools Server 是现有后端 service 层的另一个入口,不是独立系统。
| 对比 | 现有 HTTP Server (main.go) |
MCP Tools Server |
|---|---|---|
| 入口 | Fiber HTTP 路由 | stdio JSON-RPC |
| 调用方 | 前端浏览器 | Kiro CLI (AI Agent) |
| 认证 | Casdoor JWT | 无需(本地 stdio 进程) |
| 复用层 | controllers → services → repositories | 直接调用 services/repositories |
| 输出格式 | JSON HTTP Response | MCP Tool Result (text/JSON) |
具体复用方式:
// MCP Tools Server 的 go.mod 中引用现有 service 模块
// 方式一:同一 module,不同 main 入口
// service/cmd/mcp/main.go (MCP Server 入口)
// service/main.go (HTTP Server 入口)
// 方式二:独立 module,replace 引用
// mcp-servers/tools/go.mod:
// require fwh-toolbox-server v0.0.0
// replace fwh-toolbox-server => ../../service
推荐方式一(同一 module):在 service/cmd/mcp/ 下创建 MCP Server 入口,共享整个 service 代码库。
service/
├── main.go # 现有 HTTP Server 入口
├── cmd/
│ └── mcp/
│ └── main.go # MCP Tools Server 入口
├── services/ # 共享:业务逻辑
├── repositories/ # 共享:数据访问
├── domain/ # 共享:领域模型
├── dto/ # 共享:DTO
├── config/ # 共享:配置
├── database/ # 共享:数据库连接
└── cache/ # 共享:缓存
MCP Server 入口初始化流程:
// service/cmd/mcp/main.go
func main() {
// 1. 加载配置(同 HTTP Server)
cfg, _ := config.LoadConfig()
// 2. 初始化数据库连接(同 HTTP Server)
database.Init(&cfg.App)
cache.Init(cfg.App.RedisAddr)
repositories.PlaneWebHttpInit(&cfg.PlaneWebApi)
// 3. 创建 MCP Server(替代 Fiber HTTP 路由)
s := server.NewMCPServer("fwh-tools", "1.0.0",
server.WithToolCapabilities(false),
server.WithRecovery(),
)
// 4. 注册 Tools(调用 services/ 层函数)
tools.RegisterSiteInfoTools(s)
tools.RegisterSimCardTools(s)
tools.RegisterPowerFlowTools(s)
tools.RegisterAlarmTools(s)
tools.RegisterShutdownTools(s)
tools.RegisterPlaneTools(s)
// 5. 启动 stdio 通信(替代 app.Listen)
server.ServeStdio(s)
}
不需要的部分:
知识库是一个独立的 VitePress 文档项目,通过 vitepress-plugin-mcp 插件将文档暴露为 MCP Server。
| 项目 | 说明 |
|---|---|
| 位置 | 独立 Git 仓库(不在 FranklinWH-ToolBox 内) |
| 框架 | VitePress(Markdown 文档站) |
| 内容 | 团队知识:编码规范、架构模板、技术文档、最佳实践等 |
| MCP 插件 | vitepress-plugin-mcp(npm 包) |
| MCP 传输 | Streamable HTTP(远程 MCP Server) |
| 配置层级 | ./.kiro/settings/mcp.json(后端运行目录级别) |
Step 1:创建 VitePress 项目
mkdir team-knowledge && cd team-knowledge
pnpm init
pnpm add -D vitepress vitepress-plugin-mcp
mkdir docs
Step 2:编写文档内容
team-knowledge/
├── docs/
│ ├── index.md # 首页
│ ├── coding/
│ │ ├── frontend.md # 前端编码规范
│ │ ├── backend.md # 后端编码规范
│ │ └── git.md # Git 提交规范
│ ├── architecture/
│ │ ├── vue-project.md # Vue 项目架构模板
│ │ ├── go-service.md # Go 后端架构模板
│ │ └── mcp-server.md # MCP Server 开发指南
│ ├── devops/
│ │ ├── deployment.md # 部署流程
│ │ └── monitoring.md # 监控方案
│ └── troubleshooting/
│ ├── common-issues.md # 常见问题排查
│ └── fwh-specific.md # FranklinWH 业务相关
├── .vitepress/
│ └── config.ts # VitePress + MCP 配置
├── package.json
└── pnpm-lock.yaml
Step 3:配置 VitePress + MCP 插件
// .vitepress/config.ts
import { defineConfig } from 'vitepress'
import { MCPPlugin } from 'vitepress-plugin-mcp'
export default defineConfig({
title: 'Team Knowledge Base',
description: '团队知识库',
themeConfig: {
search: {
provider: 'local', // 必须启用 local search(插件依赖它的索引)
options: {},
},
sidebar: [
{
text: '编码规范',
items: [
{ text: '前端', link: '/coding/frontend' },
{ text: '后端', link: '/coding/backend' },
{ text: 'Git', link: '/coding/git' },
],
},
{
text: '架构模板',
items: [
{ text: 'Vue 项目', link: '/architecture/vue-project' },
{ text: 'Go 后端', link: '/architecture/go-service' },
],
},
],
},
vite: {
plugins: [
MCPPlugin({ port: 4100 }), // MCP Server 监听端口
],
},
})
Step 4:启动
# 开发模式(同时启动 VitePress + MCP Server)
pnpm vitepress dev docs
# MCP Server 自动在 http://localhost:4100 启动
Step 5:验证 MCP Server
# 测试 MCP Server 是否可用
curl http://localhost:4100/mcp
vitepress-plugin-mcp 自动提供以下工具:
| Tool 名称 | 描述 | 参数 |
|---|---|---|
search_documentation |
全文搜索文档内容(基于 VitePress local search 索引) | query: string |
get_page_content |
获取指定页面的完整 Markdown 内容 | path: string |
list_pages |
列出所有文档页面 | 无 |
知识库是远程 MCP Server(HTTP),在后端运行目录的 Kiro 配置中使用 url 字段连接:
./.kiro/settings/mcp.json 中添加:
{
"mcpServers": {
"team-knowledge": {
"url": "http://lochost:4100/mcp"
}
}
}
| 环境 | 方式 | 地址 |
|---|---|---|
| 本地开发 | pnpm vitepress dev docs |
http://localhost:4100/mcp |
| 团队共享 | 部署到内网服务器(PM2/systemd 常驻) | http://knowledge.internal:4100/mcp |
| Docker | 容器化部署 | http://knowledge:4100/mcp |
PM2 部署示例:
# ecosystem.config.js
module.exports = {
apps: [{
name: 'knowledge-mcp',
script: 'npx',
args: 'vitepress dev docs --port 5173',
cwd: '/opt/team-knowledge',
env: { NODE_ENV: 'production' }
}]
}
注意: vitepress-plugin-mcp 的 MCP Server 与 VitePress dev server 绑定运行。生产环境需要保持 vitepress dev 常驻运行(不是 vitepress build)。
配置完成后,在 Kiro CLI 中可以直接使用知识库:
> 帮我查一下前端编码规范中关于组件命名的约定
Kiro 会自动调用 team-knowledge MCP Server 的 search_documentation 工具,
搜索知识库内容并返回相关文档片段。
1. 在 team-knowledge 仓库中编写 Markdown 文件
2. git commit && git push
3. 服务器自动重启(PM2 watch 或 CI/CD)
4. MCP Server 自动索引新内容
5. Kiro 下次搜索即可获取新文档
| 项目 | 选择 | 理由 |
|---|---|---|
| 语言 | Go | 与后端一致,直接 import service 层代码 |
| SDK | github.com/mark3labs/mcp-go |
社区最活跃的 Go MCP SDK |
| Transport | stdio | Kiro CLI 本地调用,最简单 |
| 构建产物 | 单二进制fwh-mcp-server |
go build -o fwh-mcp-server ./cmd/mcp |
| Tool 名称 | 输入参数 | 描述 | 复用的 service 函数 |
|---|---|---|---|
query_site_info |
sn: string |
查询站点设备详细信息 | services.GetSiteInfoData |
search_sites |
page, size: int, keyword?: string |
搜索站点列表 | services.QuerySiteInfoDataList |
query_sim_card |
giccid: string |
查询单张 SIM 卡状态 | services.GetSimCardInfo |
search_sim_cards |
page, size: int, status?, platform? |
搜索 SIM 卡列表 | services.GetSimCardBatchInfo |
query_power_flow |
sn: string, date: string |
查询站点功率流数据 | services.QuerySitePowerFlow |
query_alarm_statistics |
date?: string |
查询告警统计 | services.GetAlarmStatistics |
query_shutdown_statistics |
date: string |
查询停机统计 | services.GetShutdownStatistics |
create_feedback |
title, type, priority, description |
创建 Plane Intake Issue | services.SubmitFeedback |
query_feedback_history |
无 | 查询 Feedback 提交历史 | services.GetFeedbackHistory |
service/
├── cmd/
│ └── mcp/
│ ├── main.go # MCP Server 入口
│ └── tools/
│ ├── siteInfo.go # 站点信息相关 tools
│ ├── simCard.go # SIM 卡相关 tools
│ ├── powerFlow.go # 功率流相关 tools
│ ├── alarm.go # 告警统计 tools
│ ├── shutdown.go # 停机统计 tools
│ └── plane.go # Plane/Feedback tools
├── main.go # 现有 HTTP Server(不改动)
├── services/ # 共享
├── repositories/ # 共享
└── ...
入口 service/cmd/mcp/main.go:
package main
import (
"fmt"
"log"
"os"
"github.com/mark3labs/mcp-go/server"
"fwh-toolbox-server/cache"
"fwh-toolbox-server/config"
"fwh-toolbox-server/database"
"fwh-toolbox-server/repositories"
"fwh-toolbox-server/cmd/mcp/tools"
)
func main() {
// 加载配置
cfg, err := config.LoadConfig()
if err != nil {
log.Fatalf("加载配置失败: %v", err)
}
// 初始化基础设施(与 HTTP Server 相同)
if err := database.Init(&cfg.App); err != nil {
log.Fatalf("数据库初始化失败: %v", err)
}
cache.Init(cfg.App.RedisAddr)
repositories.PlaneWebHttpInit(&cfg.PlaneWebApi)
// 创建 MCP Server
s := server.NewMCPServer(
"fwh-tools",
"1.0.0",
server.WithToolCapabilities(false),
server.WithRecovery(),
)
// 注册所有 Tools
tools.RegisterSiteInfoTools(s)
tools.RegisterSimCardTools(s)
tools.RegisterPowerFlowTools(s)
tools.RegisterAlarmTools(s)
tools.RegisterShutdownTools(s)
tools.RegisterPlaneTools(s)
// 启动 stdio 通信
fmt.Fprintln(os.Stderr, "[fwh-mcp-server] started (stdio)")
if err := server.ServeStdio(s); err != nil {
log.Fatalf("[fwh-mcp-server] error: %v", err)
}
}
工具示例 service/cmd/mcp/tools/siteInfo.go:
package tools
import (
"context"
"encoding/json"
"fmt"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"fwh-toolbox-server/services"
)
func RegisterSiteInfoTools(s *server.MCPServer) {
// Tool: 查询站点信息
s.AddTool(
mcp.NewTool("query_site_info",
mcp.WithDescription("查询单个站点设备的详细信息,包括版本、网络状态、激活状态、SN、固件等"),
mcp.WithString("sn", mcp.Required(), mcp.Description("站点 SN 序列号")),
),
handleQuerySiteInfo,
)
// Tool: 搜索站点列表
s.AddTool(
mcp.NewTool("search_sites",
mcp.WithDescription("搜索站点设备列表,支持分页"),
mcp.WithNumber("page", mcp.Description("页码,从1开始,默认1")),
mcp.WithNumber("size", mcp.Description("每页数量,默认10")),
mcp.WithString("keyword", mcp.Description("搜索关键字(匹配 SN、设备名)")),
),
handleSearchSites,
)
}
func handleQuerySiteInfo(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
sn, err := req.RequireString("sn")
if err != nil {
return mcp.NewToolResultError("参数 sn 必填"), nil
}
data, err := services.GetSiteInfoData(sn)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("查询失败: %v", err)), nil
}
jsonBytes, _ := json.MarshalIndent(data, "", " ")
return mcp.NewToolResultText(string(jsonBytes)), nil
}
func handleSearchSites(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
page := int(req.GetFloat("page", 1))
size := int(req.GetFloat("size", 10))
keyword := req.GetString("keyword", "")
data, err := services.QuerySiteInfoDataList(uint(page), uint(size), keyword, nil, nil, nil)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("搜索失败: %v", err)), nil
}
jsonBytes, _ := json.MarshalIndent(data, "", " ")
return mcp.NewToolResultText(string(jsonBytes)), nil
}
直接使用社区开源 Server,不需要自己实现。不使用 Git MCP Server。
| Server | 用途 | 包名 |
|---|---|---|
| AWS Documentation | 搜索 AWS 文档、最佳实践 | @aws/aws-documentation-mcp-server |
| Brave Search | 网络搜索(技术问题排查) | @modelcontextprotocol/server-bravesearch |
所有 Kiro 配置基于后端程序运行目录(service/)的相对路径 ./.kiro/。
service/.kiro/settings/mcp.json:
{
"mcpServers": {
"fwh-tools": {
"command": "./fwh-mcp-server",
"args": [],
"env": {
"MONGO_DSN": "${MONGO_DSN}",
"REDIS_ADDR": "${REDIS_ADDR}",
"PLANE_WEB_HOST": "${PLANE_WEB_HOST}",
"PLANE_WEB_EMAIL": "${PLANE_WEB_EMAIL}",
"PLANE_WEB_PASSWORD": "${PLANE_WEB_PASSWORD}",
"PLANE_WEB_WORKSPACE_SLUG": "${PLANE_WEB_WORKSPACE_SLUG}",
"PLANE_WEB_PROJECT_ID": "${PLANE_WEB_PROJECT_ID}"
}
},
"team-knowledge": {
"url": "http://knowledge.internal:4100/mcp"
},
"aws-docs": {
"command": "npx",
"args": ["-y", "@aws/aws-documentation-mcp-server"]
},
"web-search": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-bravesearch"],
"env": {
"BRAVE_API_KEY": "${BRAVE_API_KEY}"
}
}
}
}
service/.kiro/steering/project.md:
# FranklinWH-ToolBox 项目上下文
## 项目概述
内部工具平台,前端 Vue3 + Naive UI,后端 Go Fiber + MongoDB。
## 可用 MCP 工具
- fwh-tools: 查询站点、SIM卡、功率流、告警、Plane Issue
- team-knowledge: 搜索团队知识库文档
- aws-docs: 搜索 AWS 文档
- web-search: 网络搜索
## 注意事项
- 不要直接操作 Git
- 修改代码后需 go build / npm run build 验证
- 后端分层:controllers → services → repositories
/var/www/fwh-toolbox/server/ # 后端运行目录(WorkingDirectory)
├── fwh-toolbox-server # HTTP Server 二进制
├── fwh-mcp-server # MCP Tools Server 二进制
├── .kiro/
│ ├── settings/
│ │ └── mcp.json # MCP Server 配置(command: "./fwh-mcp-server")
│ └── steering/
│ └── project.md # Agent 指令文件
└── .env # 环境变量配置
源码目录结构:
service/
├── .kiro/ # Kiro 配置(开发环境 + 部署时拷贝)
│ ├── settings/
│ │ └── mcp.json
│ └── steering/
│ └── project.md
├── cmd/
│ └── mcp/
│ ├── main.go # MCP Server 入口
│ └── tools/ # 各工具实现
│ ├── siteInfo.go
│ ├── simCard.go
│ ├── powerFlow.go
│ ├── alarm.go
│ ├── shutdown.go
│ └── plane.go
├── main.go # HTTP Server 入口
└── ...
ACP Client 启动 kiro-cli acp 时指定工作目录为后端运行目录,Kiro 自动从 ./.kiro/settings/mcp.json 加载配置:
cmd := exec.Command("kiro-cli", "acp")
cmd.Dir = serviceDir // 后端运行目录(含 .kiro/ 和 fwh-mcp-server)
cd service
go build -o fwh-mcp-server ./cmd/mcp
# 直接运行测试
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | ./fwh-mcp-server
# 通过 Kiro CLI 测试(在 service/ 目录下)
kiro chat # 然后在对话中使用工具
| 阶段 | 内容 | 输出物 | 预估时间 |
|---|---|---|---|
| Phase 1 | 创建service/cmd/mcp/ 目录结构和入口 |
main.go + go build 通过 | 0.5 天 |
| Phase 2 | 实现站点信息 Tools | tools/siteInfo.go | 1 天 |
| Phase 3 | 实现 SIM 卡 + 功率流 Tools | tools/simCard.go, powerFlow.go | 1 天 |
| Phase 4 | 实现告警 + Plane Tools | tools/alarm.go, plane.go | 1 天 |
| Phase 5 | 配置.kiro/ + Steering + 联调 |
配置文件 + 端到端测试 | 0.5 天 |
| Phase 6 | 配置外部 MCP Server (AWS Docs, Brave) | npm 安装 + 环境变量配置 | 0.5 天 |
总计:约 4.5 天
┌────────────────────────────────┐
│ 前端 (Vue 3) │
│ src/views/ai-chat/ │
│ ┌──────────────────────────┐ │
│ │ ChatPanel 组件 │ │
│ │ • 消息列表 (Markdown) │ │
│ │ • 输入框 │ │
│ │ • 流式输出显示 │ │
│ └────────────┬─────────────┘ │
│ │ SSE / fetch │
└───────────────┼────────────────┘
│
▼
┌────────────────────────────────┐
│ 后端 (Go Fiber) │
│ service/controllers/ │
│ aiChatControllers.go │
│ ┌──────────────────────────┐ │
│ │ POST /ai/chat │ │
│ │ • 接收用户消息 │ │
│ │ • 调用 LLM API │ │
│ │ • SSE 流式返回 │ │
│ └────────────┬─────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────┐ │
│ │ LLM Provider │ │
│ │ • Claude API (Anthropic) │ │
│ │ • 或 OpenAI API │ │
│ │ • 或 AWS Bedrock │ │
│ └──────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────┐ │
│ │ MCP Tools (可选) │ │
│ │ • 复用 services 层 │ │
│ │ • AI 可调用内部工具 │ │
│ └──────────────────────────┘ │
└────────────────────────────────┘
选择 SSE 而非 WebSocket 的原因:
后端实现 ACP (Agent Client Protocol) Client,通过 JSON-RPC 2.0 与 kiro-cli acp 子进程结构化通讯。
为什么选 ACP 而非直接 exec Kiro CLI:
| 对比项 | exec kiro chat | ACP Client |
|---|---|---|
| 通讯格式 | 非结构化文本输出 | JSON-RPC 2.0 结构化 |
| 会话管理 | 自己实现 | Kiro 原生支持(session/new, session/load) |
| 工具调用可见性 | 无法感知 | ToolCall/ToolCallUpdate 实时事件 |
| 取消操作 | kill 进程 | session/cancel 优雅取消 |
| 会话持久化 | 自己实现 history.json | Kiro 自动持久化到 ~/.kiro/sessions/ |
| 协议稳定性 | CLI 输出格式可能变 | ACP 是开放标准 |
架构:
前端 SSE ←── 后端 (ACP Client) ←── JSON-RPC stdin/stdout ←── kiro-cli acp
│
├── 自动调用 MCP Server
├── 自动管理会话上下文
└── 自动持久化对话历史
POST /api/ai/chat — 发送消息
请求:
{
"message": "帮我查一下 SN 为 ABC123 的站点信息",
"sessionId": "sess_abc123" // 可选,不传则使用默认会话
}
响应(SSE 流,将 ACP 事件转发给前端):
Content-Type: text/event-stream
event: message
data: {"type":"text","content":"正在查询站点信息..."}
event: tool_call
data: {"type":"tool_call","name":"query_site_info","status":"running"}
event: tool_update
data: {"type":"tool_update","name":"query_site_info","status":"completed"}
event: message
data: {"type":"text","content":"该站点的固件版本是 v3.2.1..."}
event: done
data: {"type":"done","sessionId":"sess_abc123"}
SSE 事件映射:
| SSE Event | ACP Update Type | 说明 |
|---|---|---|
message |
AgentMessageChunk |
AI 文本流(逐块推送) |
tool_call |
ToolCall |
Kiro 调用 MCP 工具 |
tool_update |
ToolCallUpdate |
工具执行进度 |
done |
TurnEnd |
本轮对话结束 |
error |
- | 错误 |
其他接口:
| 接口 | 方法 | 描述 |
|---|---|---|
/ai/chat |
POST | 发送消息,SSE 流式返回 |
/ai/sessions |
GET | 获取当前用户会话列表 |
/ai/sessions/new |
POST | 创建新会话 |
/ai/sessions/:id |
DELETE | 删除会话 |
/ai/cancel |
POST | 取消当前操作 |
核心数据结构:
// ACPSession 单个用户的 ACP 连接
type ACPSession struct {
UserId string
SessionId string // Kiro ACP session ID
Process *exec.Cmd // kiro-cli acp 长驻进程
Stdin io.WriteCloser // JSON-RPC 请求写入
Stdout *bufio.Reader // JSON-RPC 响应/通知读取
Mutex sync.Mutex // 同一用户串行对话
LastActive time.Time
RequestId int64 // JSON-RPC 请求 ID 自增
}
// ACPManager 管理所有用户的 ACP 会话
type ACPManager struct {
sessions map[string]*ACPSession // userId -> session
mu sync.RWMutex
maxIdle time.Duration // 空闲超时
projectDir string // 项目目录
}
完整通讯流程:
// 1. 启动 kiro-cli acp(每用户一个长驻进程)
func (m *ACPManager) GetOrCreate(userId string) (*ACPSession, error) {
if sess, exists := m.sessions[userId]; exists {
sess.LastActive = time.Now()
return sess, nil
}
cmd := exec.Command("kiro-cli", "acp")
cmd.Dir = m.projectDir // 含 .kiro/settings/mcp.json
stdin, _ := cmd.StdinPipe()
stdout, _ := cmd.StdoutPipe()
cmd.Start()
sess := &ACPSession{
UserId: userId,
Process: cmd,
Stdin: stdin,
Stdout: bufio.NewReader(stdout),
}
// 2. initialize 握手
sess.sendRPC("initialize", map[string]any{
"protocolVersion": 1,
"clientCapabilities": map[string]any{
"fs": map[string]any{"readTextFile": true, "writeTextFile": true},
},
"clientInfo": map[string]any{"name": "fwh-toolbox", "version": "1.0.0"},
})
// 3. 创建会话
result := sess.sendRPC("session/new", map[string]any{"cwd": m.projectDir})
sess.SessionId = result.SessionId
m.sessions[userId] = sess
return sess, nil
}
// 4. 发送消息并流式读取
func (sess *ACPSession) Prompt(message string, events chan<- SSEEvent) {
sess.Mutex.Lock()
defer sess.Mutex.Unlock()
sess.sendRPC("session/prompt", map[string]any{
"sessionId": sess.SessionId,
"content": []map[string]any{{"type": "text", "text": message}},
})
// 持续读取 session/notification 直到 TurnEnd
for {
notif := sess.readNotification()
switch notif.Params.UpdateType {
case "AgentMessageChunk":
events <- SSEEvent{Type: "message", Content: notif.Params.Text}
case "ToolCall":
events <- SSEEvent{Type: "tool_call", Name: notif.Params.ToolName}
case "ToolCallUpdate":
events <- SSEEvent{Type: "tool_update", Name: notif.Params.ToolName}
case "TurnEnd":
events <- SSEEvent{Type: "done", SessionId: sess.SessionId}
close(events)
return
}
}
}
// 5. 取消操作
func (sess *ACPSession) Cancel() {
sess.sendRPC("session/cancel", map[string]any{"sessionId": sess.SessionId})
}
Controller 层:
func AiChat(c *fiber.Ctx) error {
userId := middleware.AuthGetUserName(c)
var req struct {
Message string `json:"message"`
SessionId string `json:"sessionId"`
}
c.BodyParser(&req)
sess, err := acpManager.GetOrCreate(userId)
if err != nil {
return Error(c, "AI 会话创建失败")
}
// SSE 流式响应
c.Set("Content-Type", "text/event-stream")
c.Set("Cache-Control", "no-cache")
c.Context().SetBodyStreamWriter(func(w *bufio.Writer) {
events := make(chan SSEEvent, 50)
go sess.Prompt(req.Message, events)
for event := range events {
fmt.Fprintf(w, "event: %s\ndata: %s\n\n", event.Type, event.JSON())
w.Flush()
}
})
return nil
}
Kiro ACP 原生支持多会话,后端无需自己实现持久化。
ACP 会话方法:
| 方法 | 用途 |
|---|---|
session/new |
创建新会话(Kiro 分配 ID,自动持久化) |
session/load |
按 ID 恢复历史会话 |
session/prompt |
在指定会话中对话 |
session/cancel |
取消当前操作 |
会话生命周期:
用户首次 → 启动 kiro-cli acp 进程 → initialize → session/new
│
用户对话 → session/prompt → 读取 notifications → SSE │ Kiro 自动
│ 持久化
用户加载历史 → session/load(sessionId) │
▼
空闲 30min → kill 进程 → 下次使用时重启 + session/load 恢复
Kiro 自动持久化目录:
~/.kiro/sessions/cli/
├── <session-id>.json # 会话元数据
└── <session-id>.jsonl # 对话事件日志
| 项目 | 说明 |
|---|---|
| 并发限制 | 同一用户 Mutex 串行(ACP 不支持同 session 并行 prompt) |
| 最大进程数 | 限制 20 个并发 kiro-cli acp 进程 |
| 空闲超时 | 30 分钟无活动 kill 进程(再次使用时自动恢复) |
| 请求超时 | 单次 prompt 最多等待 120 秒 |
| MCP 配置 | 所有会话共享项目 .kiro/settings/mcp.json |
| Kiro 订阅 | 需确保服务器上 Kiro 已登录且订阅有效 |
实现位置: src/layout/header/Actions 中,作为顶部栏全局操作按钮,点击展开 AI Chat 侧边抽屉。
目录结构:
src/layout/header/Actions/
├── AiChat/
│ ├── index.vue # 入口按钮 + NDrawer 容器
│ ├── ChatPanel.vue # 聊天面板(消息列表 + 输入)
│ ├── MessageItem.vue # 单条消息(支持 Markdown 渲染)
│ ├── ToolCallCard.vue # 工具调用展示卡片
│ ├── StreamingText.vue # 流式文本显示组件
│ ├── useAiChat.ts # 核心逻辑:SSE 连接、消息管理
│ └── interface.ts # 类型定义
入口组件 AiChat/index.vue:
<template>
<!-- 顶部栏按钮 -->
<NButton text @click="drawerShow = true">
<template #icon>
<span class="i-ph:chat-circle-dots" />
</template>
</NButton>
<!-- 侧边抽屉 -->
<NDrawer v-model:show="drawerShow" :width="480" placement="right">
<NDrawerContent title="AI 助手" closable>
<ChatPanel />
</NDrawerContent>
</NDrawer>
</template>
useAiChat.ts 核心逻辑:
export function useAiChat() {
const messages = ref<ChatMessage[]>([])
const isStreaming = ref(false)
async function sendMessage(content: string) {
// 添加用户消息
messages.value.push({ role: 'user', content })
isStreaming.value = true
// 添加 AI 消息占位
const aiMessage: ChatMessage = { role: 'assistant', content: '', toolCalls: [] }
messages.value.push(aiMessage)
// 发起 SSE 请求(使用框架的 fetch,自动带 token)
const response = await fetch(baseUrl + '/ai/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
message: content,
history: messages.value.slice(0, -1), // 不含当前占位
}),
})
// 读取 SSE 流
const reader = response.body!.getReader()
const decoder = new TextDecoder()
while (true) {
const { done, value } = await reader.read()
if (done) break
const text = decoder.decode(value)
// 解析 SSE 事件,逐步更新 aiMessage.content
parseSSEEvents(text, aiMessage)
}
isStreaming.value = false
}
return { messages, isStreaming, sendMessage }
}
ChatPanel.vue 基本结构:
<template>
<div class="flex h-full flex-col">
<!-- 消息列表 -->
<div class="flex-1 overflow-y-auto p-4 space-y-4">
<MessageItem v-for="msg in messages" :key="msg.id" :message="msg" />
<!-- 流式输出中的光标闪烁 -->
<span v-if="isStreaming" class="animate-pulse">▊</span>
</div>
<!-- 输入区域 -->
<div class="border-t p-4">
<NInput
v-model:value="inputText"
type="textarea"
:autosize="{ minRows: 1, maxRows: 4 }"
placeholder="输入消息...(Shift+Enter 换行)"
@keydown.enter.exact.prevent="handleSend"
/>
</div>
</div>
</template>
| 内容类型 | 渲染方式 |
|---|---|
| 普通文本 | Markdown 渲染(支持代码高亮) |
| 工具调用 | 折叠卡片,显示工具名 + 参数 + 结果 |
| 表格数据 | 自动识别 JSON 数组,渲染为表格 |
| 错误信息 | 红色提示卡片 |
| 风险 | 措施 |
|---|---|
| AI 误操作 Git | 不配置 Git MCP Server |
| 敏感数据泄露 | Tools 只返回必要字段,不返回密码/token/密钥 |
| AI 误操作 Plane | create_feedback 限制只能创建 Intake,不能删除/修改已有 Issue |
| MCP Server 崩溃 | 使用server.WithRecovery() 中间件恢复 panic |
| 环境变量泄露 | 使用${VAR} 引用,不硬编码 |
| 数据量过大 | Tools 返回结果限制最大条目数,分页查询 |
| Kiro 进程泄漏 | SessionManager 定时扫描超时进程并 kill |
| 用户并发冲突 | 同一用户 Mutex 锁,防止并行调用 Kiro |
| 磁盘占用 | 会话目录定期清理,限制历史文件大小 |
| Kiro 订阅额度 | 监控使用量,超额时返回友好提示 |
当前阶段不需要 A2A。适合引入的场景:
如果后续需要:
github.com/a2aproject/a2a-gohttps://a2aprotocol.ai