From a3710c8645287a53542908c54837d12fef95269f Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 8 May 2026 07:11:03 +0800 Subject: [PATCH] =?UTF-8?q?feat(mcp):=20domain=20types=20=E2=80=94=20Issue?= =?UTF-8?q?r=20/=20Scope=20/=20MCPToken=20+=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/mcp/domain.go | 108 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 internal/mcp/domain.go diff --git a/internal/mcp/domain.go b/internal/mcp/domain.go new file mode 100644 index 0000000..6028044 --- /dev/null +++ b/internal/mcp/domain.go @@ -0,0 +1,108 @@ +// Package mcp 实现 MCP(Model Context Protocol)Server 模块。 +// +// 模块结构(spec §2.4 决策):单 package + 多文件,与 acp / chat 同模式。 +// MCP 协议合规性由官方 github.com/modelcontextprotocol/go-sdk 承担; +// 本模块只负责: +// - mcp_tokens 表的 CRUD(admin 后台管理) +// - system token 在 ACP session 启动时签发、终止时撤销 +// - 鉴权 middleware(bearer / ?token= → user_id + scope) +// - 速率限制 +// - 工具/prompts/resources 的业务桥接(调 project / chat 现有 service) +// +// 三类核心抽象: +// - MCPToken:DB 行记录,token plaintext 仅签发时返回一次 +// - Scope:JSONB 形态的工具白名单 + project_ids 限定 +// - Issuer:'system' 或 'admin',决定生命期与是否绑定 ACP session +package mcp + +import ( + "time" + + "github.com/google/uuid" +) + +// Issuer 是 mcp_tokens.issuer 枚举。 +type Issuer string + +const ( + // IssuerSystem token 由 ACP supervisor 起 session 时自动签发, + // 必须绑定 acp_session_id,session 终止即撤销。 + IssuerSystem Issuer = "system" + // IssuerAdmin token 由 admin 后台手动签发给团队成员(含 admin 自己)。 + // 不绑定 ACP session,长期有效,admin 或持有者可主动撤销。 + IssuerAdmin Issuer = "admin" +) + +// IsValid 判断 issuer 是否为合法枚举值。 +func (i Issuer) IsValid() bool { + return i == IssuerSystem || i == IssuerAdmin +} + +// Scope 是 mcp_tokens.scope JSONB 列的 Go 反射。 +// +// 语义: +// - Tools 为 nil(JSON null/缺省)= 全工具允许;非 nil 但空切片 = 全部禁用 +// - ProjectIDs 为 nil = 全 project 允许;非 nil 空切片 = 全部禁用 +// +// JSON marshal 时 nil 字段用 omitempty 抹掉,与 spec §3.1 表头注释一致。 +type Scope struct { + Tools []string `json:"tools,omitempty"` + ProjectIDs []uuid.UUID `json:"project_ids,omitempty"` +} + +// AllowsTool 检查工具名是否在白名单。Tools 为 nil 时全允许。 +func (s *Scope) AllowsTool(name string) bool { + if s == nil || s.Tools == nil { + return true + } + for _, t := range s.Tools { + if t == name { + return true + } + } + return false +} + +// AllowsProject 检查 project ID 是否在限定列表。ProjectIDs 为 nil 时全允许。 +func (s *Scope) AllowsProject(id uuid.UUID) bool { + if s == nil || s.ProjectIDs == nil { + return true + } + for _, p := range s.ProjectIDs { + if p == id { + return true + } + } + return false +} + +// MCPToken 是 mcp_tokens 表的 Go 表示。 +// +// TokenHash 是 SHA-256(plaintext);plaintext 仅在 IssueAdmin/IssueSystemToken +// 的返回值里出现一次,DB 与本结构都不持有 plaintext。 +type MCPToken struct { + ID uuid.UUID + TokenHash []byte + UserID uuid.UUID + Name string + Issuer Issuer + Scope Scope + ACPSessionID *uuid.UUID + ExpiresAt *time.Time + LastUsedAt *time.Time + RevokedAt *time.Time + CreatedBy uuid.UUID + CreatedAt time.Time +} + +// IsActive 报告 token 当前是否可用(未撤销 + 未过期)。 +// now 显式注入便于测试;生产调用方传 time.Now()。 +func (t *MCPToken) IsActive(now time.Time) bool { + if t.RevokedAt != nil { + return false + } + if t.ExpiresAt != nil && !t.ExpiresAt.After(now) { + return false + } + return true +}