节点在线、应用在线、配置在线使用令牌查询
|
# Research — MCP HTTP æœåŠ¡
**Created**: 2026-07-16
**Status**: Complete
**Spec**: [spec.md](/NewLife/Stardust/Blob/master/.trae/specs/add-mcp-feature/plan/../spec.md)
> âš ï¸ **废弃说明(2026-08-03)**:本文åŠåŒç›®å½•æ–‡æ¡£ä¸æåˆ°çš„ `McpController`(HTTP 端点 Controller)**å·²åºŸå¼ƒå¹¶åˆ é™¤**,改为 `McpMiddleware`(ASP.NET ä¸é—´ä»¶ï¼‰æ‰¿è½½ `/mcp` 端点。
> **缘由**:`McpMiddleware` åœ¨ç®¡çº¿ä¸æ³¨å†ŒäºŽ `UseCube` 之å‰ï¼Œå¯¹ `/mcp` çŸè·¯å¤„ç†ï¼Œå¯é¿å… Cube çš„ device-id Cookie è§£æžï¼ˆ`Convert.FromBase64String`)在畸形 Cookie 时抛 `FormatException` 返回错误页,导致 MCP 客户端收到 HTML è€Œéž JSON-RPC。而 `McpController` ç» MVC 路由ä½äºŽ `UseCube` 之åŽï¼Œåœ¨çŽ°æœ‰ç®¡çº¿ä¸å§‹ç»ˆä¸å¯è¾¾ï¼ˆæ»ä»£ç ï¼‰ï¼Œæ•…åˆ é™¤ã€‚è¯¦è§ `Doc/MCP-3-客户端接入.md`。
## 概述
本文件记录 MCP HTTP æœåŠ¡è§„åˆ’é˜¶æ®µçš„å…³é”®æŠ€æœ¯å†³ç–ã€ç ”究结论与替代方案对比。所有结论å‡åŸºäºŽå¯¹ Stardust 现有代ç 库的实际探索,引用具体文件路径和行å·ã€‚
---
## å†³ç– 1:MCP æœåŠ¡æ‰¿è½½ä½ç½®
### Decision
**McpController + McpService 放在 `Stardust.Web` 项目**ï¼Œè€Œéž spec ä¸åŽŸå†™çš„ `Stardust.Server`。
### Rationale
1. **ä¾èµ–æ–¹å‘约æŸ**:`Stardust.Web` 引用 `Stardust.Server`(å•å‘ Web→Server),å之䏿ˆç«‹ã€‚è‹¥ McpService 放在 Serverï¼Œåˆ™æ— æ³•è®¿é—® Web 层的 `DeployService.Compile/Control` å’Œ `PipelineService.Trigger`,而 spec ä¸ 9 个 deploy æ¨¡å— action 全部ä¾èµ–这两个æœåŠ¡ã€‚
2. **能力分布现状**:
- Server 层æœåŠ¡ï¼š`NodeService`ã€`ConfigService`ã€`AppTokenService`ã€`RegistryService`ã€`GatewayService`ã€`MonitorService`(查询类为主)
- Web 层æœåŠ¡ï¼š`DeployService`(编译+部署)ã€`PipelineService`ï¼ˆæµæ°´çº¿è§¦å‘+å–æ¶ˆï¼‰
- 27 个 action ä¸ 9 个 deploy 类必须走 Web 层
3. **路由注册便利性**:Stardust.Web 已注册 `AddControllers` + `MapControllers`,新增 Controller æ— éœ€æ”¹åŠ¨ Startup。Stardust.Server ä¹Ÿæœ‰ç›¸åŒæœºåˆ¶ï¼Œä½†æ”¾ Web 一次性解决所有ä¾èµ–。
4. **æ•°æ®åº“é…置访问**:`StarServerSetting` 在 Server å’Œ Web 都能访问(通过 `services.AddSingleton(set)` æ³¨å†Œï¼‰ï¼Œæ— éœ€åˆ‡æ¢ã€‚
### Alternatives Considered
- **A. 放 Server,用 HttpClient åè°ƒ Web**:项目记忆ä¸å·²æœ‰"Web ä¸ç›´æŽ¥å¼•用 Server,跨项目调用用 HttpClient åå‘è°ƒ"的约æŸï¼ˆé’ˆå¯¹ `[ApiFilter]` 鉴æƒåœºæ™¯ï¼‰ã€‚但 MCP æœåŠ¡æœ¬èº«ä¸éœ€è¦èµ°é‰´æƒé“¾è·¯ï¼Œä¸”引入 HttpClient ä¼šå¢žåŠ ç½‘ç»œå¾€è¿”å¼€é”€å’Œæ•…éšœç‚¹ã€‚
- **B. 把 DeployService/PipelineService 下沉到 Server**ï¼šç ´å现有分层,且这两个æœåŠ¡ä¾èµ– Web 层的 Controller 上下文(如 ManageProvider.UserHost),è¿ç§»æˆæœ¬é«˜ã€‚
- **C. 新建独立 `Stardust.McpServer` 项目**:过度工程化,27 个 action 调用的æœåŠ¡åˆ†å¸ƒå¹¿æ³›ï¼Œæ–°å»ºé¡¹ç›®å而è¦å¼•用所有项目。
### 最终方案
- `McpController` 放 `Stardust.Web/Areas/Platform/Controllers/McpController.cs`(或 `Stardust.Web/Controllers/McpController.cs`)
- `McpService` 放 `Stardust.Web/Services/McpService.cs`
- `IMcpAction` / `McpActionBase` / `ResourceRequirement` 放 `Stardust.Web/Mcp/`
- 27 个 action 实现放 `Stardust.Web/Mcp/Actions/{Module}/`
- 3 å¼ è¡¨ï¼ˆ`McpToken`/`McpTokenResource`/`McpAudit`)放 `Stardust.Data/Platform/Model.xml`
---
## å†³ç– 2:实体表定义模å¼
### Decision
éµå¾ª Stardust.Data 现有的 XCode ORM 模å¼ï¼šåœ¨ `Stardust.Data/Platform/Model.xml` ä¸ä»¥ XML 声明表结构,è¿è¡Œ `xcode` å·¥å…·è‡ªåŠ¨ç”Ÿæˆ `{䏿–‡å}.cs`(partial,å«å—段/索引/å—æ®µå¸¸é‡ï¼‰ï¼Œæ‰‹å†™ `{䏿–‡å}.Biz.cs`(partial,å«åŸºç±» `Entity<T>`ã€æ‹¦æˆªå™¨ã€æ‰©å±•查询)。
### Rationale
1. **项目一致性**:现有所有实体(GalaxyProjectã€Nodeã€Appã€AppDeployã€AppPipeline ç‰ï¼‰å‡é‡‡ç”¨æ¤æ¨¡å¼ï¼Œè¯¦è§ `Stardust.Data/Platform/Model.xml:44-69`(GalaxyProject 定义)和 `Stardust.Data/Platform/星系项目.Biz.cs:11`(业务类)。
2. **xcode 自动é‡å†™**:项目记忆明确"xcode 自动é‡å†™ `Stardust.Data/Deployment/*.cs` / `*.Biz.cs` 并生æˆå¯¹åº” Web Controllers"——新增表åªéœ€æ›´æ–° Model.xml 并执行 `xcode` 工具,自动生æˆå®žä½“类和基础 Controller 骨架。
3. **Cube 集æˆ**:`<Option>` 节点的 `<CubeOutput>../../Stardust.Web/Areas/Platform</CubeOutput>`(`Model.xml:41`)会自动在 Web é¡¹ç›®ç”Ÿæˆ Cube EntityController,çœåŽ»æ‰‹å†™ CRUD。
### 实现è¦ç‚¹
- **命å空间**:`Stardust.Data.Platform`(由 `<Namespace>` 节点决定)
- **连接å**:`Stardust`(由 `<ConnName>` 节点决定)
- **å®¡è®¡å—æ®µ**ï¼šç»Ÿä¸€åŒ…å« `CreateUserId/CreateTime/CreateIP/UpdateUserId/UpdateTime/UpdateIP`(camelCase,与 GalaxyProject 一致),`Category="扩展"`
- **主键**:`Id`(Int32,Identity=true,PrimaryKey=true)
- **索引**:唯一索引å‰ç¼€ `IU_`,普通索引å‰ç¼€ `IX_`
- **Map 属性**:仅当需è¦å¯¼èˆªå±žæ€§æ—¶ä½¿ç”¨ï¼ˆå¦‚ `TokenId` å¯ `Map=Stardust.Data.Platform.McpToken@Id@Name` ç”Ÿæˆ `TokenName` æ˜¾ç¤ºå—æ®µï¼‰
### Alternatives Considered
- **A. 用 EF Core Code-First**:与现有 XCode 体系冲çªï¼Œå¼•入两套 ORM。
- **B. 直接手写实体类ä¸ç”¨ xcode**:失去自动生æˆå—段常é‡ã€ç´¢å¼•器ã€Cube Controller 的能力,且è¿å项目约定。
---
## å†³ç– 3ï¼šèµ„æºæŽˆæƒæ ¡éªŒçš„间接查找实现
### Decision
`ResourceRequirement.Indirect=true` 时,框架层通过预注册的"间接解æžå™¨"å—å…¸åæŸ¥èµ„æº ID 对应的 ProjectId/AppId,而éžåœ¨æ¯ä¸ª action 内é‡å¤å®žçŽ°ã€‚
### Rationale
1. **AppPipelineRun æ— ProjectId å—æ®µ**:探索å‘现 `AppPipelineRun` 表(`Deployment/Model.xml:305-335`)没有 `ProjectId` å—æ®µï¼Œå¿…须通过 `PipelineId → AppPipeline.ProjectId` 二级查找。
2. **AppDeploy 有 ProjectId**:直接查找 `AppDeploy.FindById(deploy_id).ProjectId` å³å¯ã€‚
3. **AppPipeline 有 ProjectId**:直接查找 `AppPipeline.FindById(pipeline_id).ProjectId` å³å¯ã€‚
4. **é¿å… action é‡å¤å®žçް**:27 个 action ä¸ 9 个 deploy 类都需è¦é—´æŽ¥æŸ¥æ‰¾ï¼Œè‹¥æ¯ä¸ª action 内自行实现会导致é‡å¤ä»£ç 。
### 实现方案
在 `McpService` 䏿³¨å†Œ `Dictionary<String, Func<Int32, Int32>>` 间接解æžå™¨ï¼š
```csharp
_indirectResolvers = new()
{
["AppDeploy"] = id => AppDeploy.FindById(id)?.ProjectId ?? 0,
["AppPipeline"] = id => AppPipeline.FindById(id)?.ProjectId ?? 0,
["AppPipelineRun"] = id => AppPipelineRun.FindById(id)?.PipelineId is { } pid
? AppPipeline.FindById(pid)?.ProjectId ?? 0 : 0,
["App"] = id => App.FindById(id)?.ProjectId ?? 0,
["Node"] = id => Node.FindByID(id)?.ProjectId ?? 0,
};
```
`ResourceRequirement` 声明 `IndirectEntity="AppDeploy"`,框架层自动调用解æžå™¨èŽ·å– ProjectIdï¼Œå†æ ¡éªŒ Token æ˜¯å¦æŽˆæƒè¯¥ ProjectId。
### Alternatives Considered
- **A. æ¯ä¸ª action å†…è‡ªè¡Œæ ¡éªŒ**:è¿å spec FR4.9"action 内ä¸å†é‡å¤èµ„æºæŽˆæƒæ ¡éªŒ"的约æŸã€‚
- **B. å射动æ€è°ƒç”¨**:性能差且类型ä¸å®‰å…¨ã€‚
---
## å†³ç– 4:MCP å议端点路由
### Decision
`McpController` 使用ç»å¯¹è·¯å¾„特性路由 `[Route("/mcp")]` + `[ApiController]` + `[ApiFilter]`,方法用 `[HttpPost]`。
### Rationale
1. **çŽ°æœ‰è·¯ç”±é£Žæ ¼**ï¼šé¡¹ç›®å…¨éƒ¨ä½¿ç”¨ç‰¹æ€§è·¯ç”±ï¼Œæ— `api/` å‰ç¼€ï¼Œæ— Convention。`HomeController` 用 `[Route("/")]`(`HomeController.cs:20`)ã€`NodeController` 用 `[HttpGet("/node/notify")]`(`NodeController.cs:257`ï¼‰å·²è¯æ˜Žç»å¯¹è·¯å¾„å¯è¡Œã€‚
2. **MCP åè®®è¦æ±‚**:客户端需固定端点 `POST /mcp`,ä¸èƒ½ç”¨ `/mcp/index` 或 `/mcp/call` ç‰å¤šæ®µè·¯å¾„。ç»å¯¹è·¯å¾„ `[Route("/mcp")]` + 方法级 `[HttpPost]` å¯ç²¾ç¡®å¾—到 `POST /mcp`。
3. **`[ApiFilter]` 适用性**:`[ApiFilter]` 统一处ç†å¼‚常包装和å“åº”æ ¼å¼åŒ–。但 MCP åè®®è¦æ±‚返回 JSON-RPC 2.0 æ ¼å¼ï¼ˆ`{"jsonrpc":"2.0","id":...,"result":...}`),与 `[ApiFilter]` 默认的 `{code:0,data:...}` æ ¼å¼å†²çªã€‚
### 关键调整
**MCP 端点ä¸ä½¿ç”¨ `[ApiFilter]`**,å‚考 `PipelineWebhookController`ï¼ˆé¡¹ç›®è®°å¿†ä¸æåˆ°"Web API:`/Pipeline/Webhook`ï¼Œæ— `[ApiFilter]`")。MCP Controller 直接返回 JSON-RPC æ ¼å¼çš„ `JsonResult`,异常自行包装为 JSON-RPC error。
### Controller 骨架
```csharp
[ApiController]
[Route("/mcp")]
public class McpController(McpService mcpService, StarServerSetting setting) : ControllerBase
{
[HttpPost]
public async Task<IActionResult> Handle()
{
if (!setting.EnableMcp) return NotFound();
using var reader = new StreamReader(Request.Body);
var body = await reader.ReadToEndAsync();
var response = await mcpService.HandleAsync(body, UserHost);
return Json(response);
}
}
```
### Alternatives Considered
- **A. 用 `[Route("[controller]")]`**:得到 `/mcp`,但ä¾èµ– Controller 命å为 `McpController`ï¼Œä¸”æ— æ³•ç²¾ç¡®æŽ§åˆ¶å•æ®µè·¯å¾„。
- **B. 用 `[Route("mcp")]`**:ç‰ä»·äºŽ A,得到 `/mcp`,但相对路径在æŸäº›è·¯ç”±é…置下å¯èƒ½è¢«æ‹¼æŽ¥å‰ç¼€ã€‚
---
## å†³ç– 5:IMcpAction 接å£ä¸Žå射注册机制
### Decision
`IMcpAction` 接å£å®šä¹‰ 5 个åªè¯»å±žæ€§ + 1 个 `InvokeAsync` 方法。`McpService` åœ¨æž„é€ å‡½æ•°ä¸é€šè¿‡ `Assembly.GetTypes()` åå°„æ‰«ææ‰€æœ‰å®žçŽ°ç±»ï¼Œè‡ªåŠ¨å®žä¾‹åŒ–å¹¶æ³¨å†Œåˆ° `Dictionary<String, IMcpAction>`。
### Rationale
1. **扩展性需求**:spec FR7.6 è¦æ±‚"新增动作åªéœ€æ·»åŠ å®žçŽ°ç±»ï¼Œæ— éœ€ä¿®æ”¹ McpService"。
2. **é¡¹ç›®é£Žæ ¼ä¸€è‡´**:Stardust.Server çš„ Controller 也是通过 `AddControllers()` 自动å‘çŽ°æ³¨å†Œï¼Œæ— æ˜¾å¼æ³¨å†Œä»£ç 。
3. **ä¾èµ–注入兼容**:实现类å¯é€šè¿‡ä¸»æž„é€ å‡½æ•°æ³¨å…¥ Service(如 `StarFactory`ã€`DeployService`ã€`ConfigService`)。
### 实现方案
```csharp
public interface IMcpAction
{
String Name { get; } // snake_case,如 node_send_command
String Description { get; } // é¢å‘ LLM 的功能说明
String Module { get; } // node/app/config/deploy/gateway/monitor/system
JObject InputSchema { get; } // JSON Schema 傿•°å®šä¹‰
ResourceRequirement? RequiredResource { get; } // null è¡¨ç¤ºæ— èµ„æºä¾èµ–
Task<Object> InvokeAsync(JsonElement params, CancellationToken ct);
}
public sealed class ResourceRequirement
{
public String ResourceType { get; init; } // project / node / app
public String IdField { get; init; } // params ä¸çš„å—æ®µå
public Boolean Indirect { get; init; } // 是å¦éœ€é—´æŽ¥æŸ¥æ‰¾
public String? IndirectEntity { get; init; } // 间接查找的实体å
}
```
`McpService` 注册逻辑:
```csharp
public McpService(IServiceProvider sp)
{
_sp = sp;
_actions = new();
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
{
if (type.IsClass && !type.IsAbstract && typeof(IMcpAction).IsAssignableFrom(type))
{
var action = (IMcpAction)ActivatorUtilities.CreateInstance(sp, type);
_actions[action.Name] = action;
}
}
}
```
### 注æ„事项
- **程åºé›†èŒƒå›´**:å射需扫æ `Stardust.Web` 程åºé›†ï¼ˆ`Assembly.GetExecutingAssembly()`ï¼‰ï¼Œä¸æ˜¯ `Stardust.Server`。
- **ä¾èµ–注入**:使用 `ActivatorUtilities.CreateInstance` æ”¯æŒ action æž„é€ å‡½æ•°æ³¨å…¥ Service。
- **å•例 vs Scoped**:action 实例在 McpService æž„é€ æ—¶ä¸€æ¬¡æ€§åˆ›å»ºï¼Œç”±äºŽä¾èµ–çš„ Service 都是 Singleton,action 实质上也是 Singleton。
### Alternatives Considered
- **A. 用 `IServiceCollection.AddSingleton<IMcpAction, XxxAction>()` æ˜¾å¼æ³¨å†Œ**:è¿å spec FR7.6"æ— éœ€ä¿®æ”¹ McpService 注册代ç "。
- **B. 用 Source Generator ç¼–è¯‘æ—¶ç”Ÿæˆæ³¨å†Œä»£ç **:过度工程化,åå°„ä¸€æ¬¡æ€§æˆæœ¬å¯æŽ¥å—(å¯åŠ¨æ—¶ 27 个类)。
---
## å†³ç– 6:`pipeline_cancel` 实现方案
### Decision
新增 `PipelineService.Cancel(Int64 runId, String userHost)` 方法,将 `AppPipelineRun.Status` 从 `Pending`/`Building`/`Deploying` 改为 `Cancelled`,并å°è¯•䏿–æ£åœ¨æ‰§è¡Œçš„ `RunAsync` 任务。
### Rationale
1. **能力缺失确认**:探索å‘现 `PipelineService` 现有方法仅有 `Trigger`/`Reprocess`/`HandleWebhookAsync`ï¼Œæ— `Cancel`。但 `PipelineStatus` æžšä¸¾å·²åŒ…å« `Cancelled` å€¼ï¼Œè¯´æ˜Žè®¾è®¡ä¸Šé¢„ç•™äº†å–æ¶ˆè¯ä¹‰ã€‚
2. **å–æ¶ˆç²’度**:由于 `RunAsync` 内部调用 `DeployService.Compile` å’Œ `DeployService.Control`,真æ£çš„ç¡¬ä¸æ–(kill 进程)难以实现。采用"æ ‡è®°å–æ¶ˆ + è½¯ä¸æ–"ç–略:
- 设置 `AppPipelineRun.Status = Cancelled`
- `RunAsync` 在æ¯ä¸ªé˜¶æ®µåˆ‡æ¢ç‚¹æ£€æŸ¥ `Status == Cancelled`,若是则终æ¢åŽç»æ¥éª¤
- 已下å‘的编译/éƒ¨ç½²å‘½ä»¤æ— æ³•æ’¤é”€ï¼ˆèŠ‚ç‚¹ä¼šç»§ç»æ‰§è¡Œï¼‰ï¼Œä½†æµæ°´çº¿ä¸å†ç‰å¾…结果
### 实现方案
```csharp
public class PipelineService(...)
{
public async Task<Boolean> Cancel(Int64 runId, String userHost)
{
var run = AppPipelineRun.FindById(runId);
if (run == null) return false;
if (run.Status is PipelineStatus.Success or PipelineStatus.Failed or PipelineStatus.Cancelled)
return false; // 已终æ€ï¼Œä¸å¯å–消
run.Status = PipelineStatus.Cancelled;
run.Update();
// 写历å²è®°å½•
run.WriteHistory("å–æ¶ˆæµæ°´çº¿", true, $"ç”± {userHost} å–æ¶ˆ", userHost);
return true;
}
}
```
`RunAsync` 内部在编译å‰ã€éƒ¨ç½²å‰æ£€æŸ¥ï¼š
```csharp
if (run.Status == PipelineStatus.Cancelled)
{
_logger.LogInformation("æµæ°´çº¿ {RunId} 已喿¶ˆï¼Œè·³è¿‡åŽç»æ¥éª¤", run.Id);
return;
}
```
### Alternatives Considered
- **A. 用 CancellationTokenSource ç¡¬ä¸æ–**ï¼šéœ€é‡æž„ `RunAsync` å…¨é“¾è·¯ä¼ é€’ tokenï¼Œæ”¹åŠ¨å¤§ä¸”æ— æ³•ä¸æ–已下å‘的节点命令。
- **B. ä¸å®žçް cancel,仅返回错误**:è¿å spec FR8.4 è¦æ±‚。
---
## å†³ç– 7:`app_restart/stop/start` 实现路径
### Decision
通过 `DeployService.Control(app, deployNode, action, ...)` èµ°éƒ¨ç½²èŠ‚ç‚¹è·¯å¾„ï¼Œè€Œéž `StarFactory.SendAppCommandAsync`。
### Rationale
1. **探索å‘现**:`DeployService.Control`(`Stardust.Web/Services/DeployService.cs:98`ï¼‰æ”¯æŒ action=`start`/`stop`/`restart`ï¼Œå†…éƒ¨ä¸‹å‘ `deploy/start`ã€`deploy/stop`ã€`deploy/restart` 命令到 StarAgent,由 StarAgent 控制应用进程。
2. **应用直接命令的é™åˆ¶**:`StarFactory.SendAppCommandAsync` 下å‘的是应用层命令(如 `app/restart`),需è¦åº”用本身实现该命令处ç†å™¨ã€‚å¹¶éžæ‰€æœ‰åº”用都支æŒã€‚
3. **部署路径的优势**:`DeployService.Control` 通过 StarAgent 控制进程,与应用是å¦å®žçް命令处ç†å™¨æ— å…³ï¼Œè¦†ç›–é¢æ›´å¹¿ã€‚
### 实现方案
`app_restart` action 内部逻辑:
```csharp
public async Task<Object> InvokeAsync(JsonElement params, CancellationToken ct)
{
var appId = params.GetProperty("app_id").GetInt32();
var app = App.FindById(appId);
var deploy = AppDeploy.FindByAppId(appId); // å–该应用的部署集
var node = AppDeployNode.FindByDeployId(deploy.Id); // å–ç›®æ ‡èŠ‚ç‚¹
await _deployService.Control(deploy, node, "restart", UserHost, 0, 0, null, ct);
return new { success = true, message = "é‡å¯å‘½ä»¤å·²ä¸‹å‘" };
}
```
### 资æºä¾èµ–声明
`RequiredResource = new() { ResourceType = "app", IdField = "app_id" }`
### 注æ„事项
- 若应用有多个部署节点,需明确æ“作哪一个。spec ä¸ `app_restart` å…¥å‚ä»… `app_id`ï¼Œéœ€è¦æ‰©å±•为 `app_id` + å¯é€‰ `node_id`(ä¸ä¼ 则æ“作主节点)。
- å®žçŽ°æ—¶ä¼˜å…ˆå– `IsMain=true` 或第一个å¯ç”¨çš„ `AppDeployNode`。
---
## å†³ç– 8:`get_audit_log` 实现
### Decision
直接查询新增的 `McpAudit` 表,按 `TokenId` è¿‡æ»¤è¿”å›žå½“å‰ Token 的调用记录。
### Rationale
1. **ä¸å¤ç”¨ AppCommand**:`AppCommand` 表记录的是 `App/SendCommand` å’Œ `Node/SendCommand` 的下å‘记录,ä¸åŒ…å« MCP 工具调用本身(如 `list_actions`ã€`search_resources` 这类查询ä¸è§¦å‘命令下å‘)。
2. **ä¸å¤ç”¨ Cube 通用日志**:`/Admin/Log` 是用户æ“作日志,粒度粗且ä¸åŒºåˆ† Token。
3. **McpAudit 已在 spec ä¸å®šä¹‰**:新增 `McpAudit` è¡¨ï¼ˆè§ spec "æ ¸å¿ƒè®¾è®¡ 8"ï¼‰ï¼Œæ¯æ¬¡ `tools.call` 都记录一æ¡ï¼Œæ˜¯ `get_audit_log` çš„å¤©ç„¶æ•°æ®æºã€‚
### 实现方案
```csharp
public async Task<Object> InvokeAsync(JsonElement params, CancellationToken ct)
{
var tokenId = McpContext.Current.TokenId;
var page = params.TryGetProperty("page", out var p) ? p.GetInt32() : 1;
var pageSize = params.TryGetProperty("page_size", out var ps) ? ps.GetInt32() : 20;
var success = params.TryGetProperty("success", out var s) ? (bool?)s.GetBoolean() : null;
var pp = new PageParameter { PageIndex = page, PageSize = pageSize };
var list = McpAudit.Search(tokenId, success, DateTime.MinValue, DateTime.Now, null, pp);
return list.Select(a => new {
a.Id, a.ToolName, a.ActionName, a.Success, a.ErrorMessage,
a.Duration, a.CreateTime
});
}
```
---
## å†³ç– 9:Token å—符串生æˆä¸Žæ ¡éªŒ
### Decision
Token å—ç¬¦ä¸²æ ¼å¼ï¼š`sdmcp_` + 32 ä½ Base62 éšæœºå—符(总长 38 ä½ï¼‰ï¼Œä½¿ç”¨ `NewLife.Rand.NextString(32, true, true, false)` 生æˆã€‚æ ¡éªŒæ—¶ä½¿ç”¨æ’å®šæ—¶é—´æ¯”è¾ƒé˜²æ¢æ—¶åºæ”»å‡»ã€‚
### Rationale
1. **å‰ç¼€è®¾è®¡**:`sdmcp_` å‰ç¼€ä¾¿äºŽè¯†åˆ« MCP Token(区别于 App.Secretã€AppPipeline.Token),调试日志ä¸ä¸€çœ¼å¯è¾¨ã€‚
2. **长度选择**:32 ä½ Base62 = 约 190 ä½ç†µï¼Œè¿œè¶…安全阈值。
3. **æ’定时间比较**:spec éžåŠŸèƒ½éœ€æ±‚è¦æ±‚"Token æ ¡éªŒä½¿ç”¨æ’å®šæ—¶é—´æ¯”è¾ƒé˜²æ—¶åºæ”»å‡»"。用 `CryptographicOperations.FixedTimeEquals` 或é€å—节 XOR 比较。
### 实现方案
```csharp
// 生æˆ
public static String GenerateToken() => "sdmcp_" + Rand.NextString(32, true, true, false);
// æ ¡éªŒï¼ˆæ’定时间)
public static Boolean SafeEquals(String a, String b)
{
if (a == null || b == null || a.Length != b.Length) return false;
var result = 0;
foreach (var (x, y) in a.Zip(b)) result |= x ^ y;
return result == 0;
}
```
### 注æ„
- Token å—符串在 `McpToken.Token` å—æ®µä¸å˜å‚¨ï¼Œé•¿åº¦ 64(spec 定义),实际å˜å‚¨ 38 å—符。
- Token å—符串ä¸åœ¨æ—¥å¿—䏿˜Žæ–‡æ‰“å°ï¼ˆspec éžåŠŸèƒ½éœ€æ±‚ï¼‰ï¼Œå®¡è®¡æ—¥å¿—çš„ `Arguments` å—æ®µéœ€è„±æ•(移除 `token`/`secret`/`authorization` å—æ®µï¼‰ã€‚
---
## å†³ç– 10:MCP å议版本与客户端兼容性
### Decision
实现 MCP å议版本 `2024-11-05`(HTTP transport),兼容主æµå®¢æˆ·ç«¯ï¼ˆClaude Desktop / Trae / Cursor)。
### Rationale
1. **å议稳定性**:`2024-11-05` 是 MCP å议首个稳定 HTTP transport 版本,主æµå®¢æˆ·ç«¯å‡å·²æ”¯æŒã€‚
2. **ä¸å®žçް SSE**:spec Out of Scope 明确"æš‚ä¸å®žçް SSE 长连接"。纯 HTTP å•请求å•å“应模å¼è¶³ä»¥æ»¡è¶³æ‰€æœ‰ action è°ƒç”¨ï¼ˆæ— æµå¼è¾“出需求)。
3. **å议方法å集**:仅实现 `initialize` / `tools.list` / `tools.call` ä¸‰ä¸ªæ ¸å¿ƒæ–¹æ³•ï¼Œä¸å®žçް `resources`/`prompts`/`notifications`(spec Out of Scope)。
### `initialize` å“åº”æ ¼å¼
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"serverInfo": {
"name": "Stardust",
"version": "1.0.0"
},
"capabilities": {
"tools": {}
}
}
}
```
### `tools.list` å“åº”æ ¼å¼
返回 5 个固定工具的 JSON Schema 定义。æ¯ä¸ªå·¥å…·çš„ `inputSchema` éµå¾ª JSON Schema draft-07。
---
## å†³ç– 11:Web 端 Token 管ç†é¡µé¢
### Decision
新增 3 个 Cube EntityController:
- `McpTokenController : EntityController<McpToken>`(`/Platform/McpToken`)
- `McpTokenResourceController : EntityController<McpTokenResource>`(`/Platform/McpTokenResource`)
- `McpAuditController : EntityController<McpAudit>`(`/Platform/McpAudit`)
Token è¡¨å•æ”¯æŒå‹¾é€‰èµ„æºï¼ˆé¡¹ç›®/节点/应用)。
### Rationale
1. **Cube 自动生æˆ**:xcode 工具会基于 Model.xml 自动生æˆåŸºç¡€ Controller 骨架到 `Stardust.Web/Areas/Platform/Controllers/`(由 `<CubeOutput>` é…置决定)。
2. **现有模å¼å‚考**:`GalaxyProjectController`(`Areas/Platform/Controllers/GalaxyProjectController.cs:14`)是 Platform Area çš„æ ‡å‡† EntityController,å¯ä½œä¸ºæ¨¡æ¿ã€‚
3. **资æºå‹¾é€‰å®žçް**:å‚考 `AppDeployNode` çš„ `_List_Toolbar_Batch.cshtml`(`Areas/Deployment/Views/AppDeployNode/_List_Toolbar_Batch.cshtml:37-40`)使用 `_Form_ListBox` 局部视图渲染多选。Token 表å•需自定义 `_Form_Body.cshtml`,分三组(项目/节点/应用)渲染勾选框。
### Token 表å•关键逻辑
```csharp
// McpTokenController.OnInsert
protected override Int32 OnInsert(McpToken entity)
{
if (entity.Token.IsNullOrEmpty())
entity.Token = McpToken.GenerateToken();
entity.CallCount = 0;
return base.OnInsert(entity);
}
// McpTokenController 陿€æž„é€
static McpTokenController()
{
ListFields.RemoveField("Token", "LastIP", "LastTime", "CallCount", "TraceId");
// Token å—æ®µåœ¨åˆ—表页éšè—,仅在详情/编辑页å¯è§
}
```
### 资æºå‹¾é€‰è§†å›¾
需新建 `Areas/Platform/Views/McpToken/_Form_Body.cshtml`:
- é历 `GalaxyProject.FindAllWithCache()` 渲染项目勾选框
- é历 `Node.FindAllWithCache()` 渲染节点勾选框
- é历 `App.FindAllWithCache()` 渲染应用勾选框
- æ¯ç»„支æŒ"全部资æº"勾选
- ä¿å˜æ—¶åœ¨ `OnInsert/OnUpdate` ä¸åŒæ¥å†™å…¥ `McpTokenResource` 表
---
## å†³ç– 12:JSON-RPC é”™è¯¯ç æ˜ å°„
### Decision
| JSON-RPC code | å«ä¹‰ | è§¦å‘æ¡ä»¶ |
|---|---|---|
| `-32700` | Parse error | JSON è§£æžå¤±è´¥ |
| `-32600` | Invalid Request | éž JSON-RPC 2.0 æ ¼å¼ |
| `-32601` | Method not found | 未识别的 method 或被 `McpActionSet` ç¦ç”¨çš„ action |
| `-32602` | Invalid params | 傿•°ç¼ºå¤±æˆ–ä¸ç¬¦åˆ `input_schema` |
| `-32603` | Internal error | 未æ•获的异常 |
| `-32001` | Unauthorized | Token ä¸å˜åœ¨/ç¦ç”¨/过期 |
| `-32002` | Timeout | 超时(默认 30 秒) |
| `-32003` | Forbidden | èµ„æºæœªæŽˆæƒ |
| `-32000` | Server error | action 业务异常 |
### Rationale
- `-32700` 到 `-32603` 是 JSON-RPC 2.0 规范预定义错误ç 。
- `-32000` 到 `-32099` 是规范ä¿ç•™çš„"server error"范围,å¯è‡ªå®šä¹‰ã€‚
- spec FR10.1-FR10.5 已定义这些错误ç ,æ¤å¤„仅补充 `-32600`(spec 未明确但åè®®è¦æ±‚)。
---
## å†³ç– 13:并å‘与性能
### Decision
- `McpService` 注册为 Singleton,内部å—å…¸ `ConcurrentDictionary<String, IMcpAction>` ä¿è¯çº¿ç¨‹å®‰å…¨ã€‚
- 啿¬¡å·¥å…·è°ƒç”¨ P95 < 500ms(ä¸å«ä¸‹æ¸¸ RPC ç‰å¾…,如 `SendNodeCommandAsync` çš„ timeout=5s)。
- æ”¯æŒ 50 å¹¶å‘ MCP 调用(Kestrel 默认并å‘上é™è¿œè¶…æ¤å€¼ï¼Œæ— 需特殊é…置)。
- `McpAudit` 写入异æ¥åŒ–(`Task.Run`),ä¸é˜»å¡žä¸»è°ƒç”¨è¿”回。
### Rationale
1. **Singleton 安全性**:所有ä¾èµ–çš„ Service 都是 Singleton,action 实例也是 Singletonï¼Œæ— çŠ¶æ€ï¼Œçº¿ç¨‹å®‰å…¨ã€‚
2. **审计异æ¥å†™å…¥**:å‚考 `NodeService.WriteHistory` çš„å¼‚æ¥æ¨¡å¼ï¼Œé¿å…审计日志写入拖慢主调用。
3. **æ•°æ®åº“è¿žæŽ¥æ± **:XCode é»˜è®¤è¿žæŽ¥æ± å¤§å° 100,50 å¹¶å‘足够。
---
## å†³ç– 14:é…ç½®é¡¹å˜æ›´ç«‹å³ç”Ÿæ•ˆ
### Decision
`EnableMcp` å’Œ `McpActionSet` 通过 `StarServerSetting`(DbConfigProvider)å˜å‚¨ï¼Œé…ç½®å˜æ›´ç«‹å³ç”Ÿæ•ˆï¼ˆæ— 需é‡å¯ï¼‰ã€‚
### Rationale
1. **DbConfigProvider 机制**:`StarServerSetting.Provider = new DbConfigProvider { ... }`(`Setting.cs:25`),é…ç½®å˜å‚¨åœ¨æ•°æ®åº“ `Config` 表ä¸ã€‚æ¯æ¬¡è®¿é—® `StarServerSetting.Current.EnableMcp` 会从数æ®åº“读å–ï¼ˆæœ‰çŸæ—¶ç¼“å˜ï¼‰ã€‚
2. **McpService è¯»å–æ–¹å¼**ï¼šæ¯æ¬¡ `/mcp` è¯·æ±‚æ—¶è¯»å– `setting.EnableMcp`,而éžå¯åŠ¨æ—¶ç¼“å˜ã€‚若关é—则直接返回 404。
3. **McpActionSet 过滤**ï¼šæ¯æ¬¡ `list_actions` å’Œ `invoke_action` æ—¶è¯»å– `setting.McpActionSet`,动æ€è¿‡æ»¤åŠ¨ä½œã€‚
---
## å†³ç– 15:缺æ¼è¯†åˆ«ä¸Žè¡¥å……
### 探索ä¸å‘现的é¢å¤–问题
1. **`app_restart/stop/start` å…¥å‚需扩展**:原 spec ä¸å…¥å‚ä»… `app_id`,但 `DeployService.Control` éœ€è¦ `AppDeployNode`(部署节点)。若应用有多个节点,需明确æ“作哪个。
- **方案**:入å‚å¢žåŠ å¯é€‰ `node_id`,ä¸ä¼ æ—¶å–主节点(第一个å¯ç”¨çš„ `AppDeployNode`)。
2. **`gateway_list_clusters` æ— ç‹¬ç«‹ Controller**:探索å‘现 `Stardust.Web/Areas/Gateway/Controllers/` ä¸‹æ— `GatewayClusterController`,但 `GatewayCluster.Search` 实体方法å˜åœ¨ã€‚MCP action 直接调用实体方法å³å¯ï¼Œä¸ä¾èµ– Controller。
3. **`AppDeploy.AppId` å—æ®µæ— Map**:`Deployment/Model.xml:48` ä¸ `AppDeploy.AppId` ä»…æè¿°"对应StarApp"ï¼Œæ— `Map` 属性。查找 AppDeploy 时需通过 `AppDeploy.FindByAppId(appId)` 或 `AppDeploy.Search(projectId, appId, ...)`。
4. **`DeployService` 两个åŒåç±»**:
- `Stardust.Server/Services/DeployService.cs`ï¼šå¤„ç† `AppDeployVersion`ã€`AppDeployNode` 与节点 Ping
- `Stardust.Web/Services/DeployService.cs`:处ç†ç¼–译/下å‘(`Compile`/`Control`)
- MCP action 应注入 Web 层的 `DeployService`(命å空间 `Stardust.Web.Services`ï¼‰ï¼Œæ³¨æ„ `using` 区分。
5. **`StarFactory.SendNodeCommand` 已废弃**:探索å‘现旧版返回 `Int32` çš„ `SendNodeCommand` å·²æ ‡æ³¨ `[Obsolete]`。MCP action 必须使用新版 `SendNodeCommandAsync`(返回 `Task<CommandReplyModel?>`)。
### 已在 spec ä¸è¦†ç›–但需在 plan 䏿˜Žç¡®
- 5 个 MCP 工具的 `inputSchema` JSON Schema å®Œæ•´å®šä¹‰ï¼ˆè§ `contracts/mcp-protocol.md`)
- 3 å¼ è¡¨çš„å®Œæ•´ Model.xml å®šä¹‰ï¼ˆè§ `data-model.md`)
- 27 个 action çš„ `RequiredResource` å£°æ˜ŽçŸ©é˜µï¼ˆè§ `plan.md`)
---
## 总结
| # | å†³ç– | 关键选择 |
|---|---|---|
| 1 | MCP æœåŠ¡æ‰¿è½½ä½ç½® | `Stardust.Web` é¡¹ç›®ï¼ˆéž Server) |
| 2 | å®žä½“è¡¨å®šä¹‰æ¨¡å¼ | XCode Model.xml + xcode è‡ªåŠ¨ç”Ÿæˆ |
| 3 | é—´æŽ¥èµ„æºæ ¡éªŒ | 预注册解æžå™¨å—å…¸ |
| 4 | MCP 端点路由 | `[Route("/mcp")]` + ä¸ç”¨ `[ApiFilter]` |
| 5 | IMcpAction 注册 | å射扫æè‡ªåŠ¨æ³¨å†Œ |
| 6 | pipeline_cancel | æ ‡è®°å–æ¶ˆ + RunAsync è½¯ä¸æ– |
| 7 | app_restart/stop/start | 走 DeployService.Control 路径 |
| 8 | get_audit_log | 查新增 McpAudit 表 |
| 9 | Token å—符串 | `sdmcp_` + 32 ä½ Base62 |
| 10 | MCP å议版本 | `2024-11-05` HTTP transport |
| 11 | Web Token ç®¡ç† | Cube EntityController + è‡ªå®šä¹‰è¡¨å• |
| 12 | JSON-RPC 错误ç | 规范ç + 自定义 `-32001~-32003` |
| 13 | 并呿€§èƒ½ | Singleton + ConcurrentDictionary + 异æ¥å®¡è®¡ |
| 14 | é…ç½®å³æ—¶ç”Ÿæ•ˆ | DbConfigProvider æ¯æ¬¡è¯»å– |
| 15 | 缺æ¼è¡¥å…… | app_restart 入傿‰©å±• + DeployService 命å空间区分 |
所有技术决ç–å·²æ˜Žç¡®ï¼Œæ— `[NEEDS CLARIFICATION]` 残留。å¯è¿›å…¥ Phase 1 设计阶段。
|