完善 MCP 工具

This commit is contained in:
2026-06-11 08:21:09 +08:00
parent c0f15050d0
commit 1415d3b43b
20 changed files with 3722 additions and 61 deletions
+175
View File
@@ -14,6 +14,10 @@ import (
func registerPMTools(srv *mcpsdk.Server, deps ServerDeps) {
registerListProjects(srv, deps)
registerCreateProject(srv, deps)
registerUpdateProject(srv, deps)
registerArchiveProject(srv, deps)
registerUnarchiveProject(srv, deps)
registerListWorkspaces(srv, deps)
registerListRequirements(srv, deps)
registerListIssues(srv, deps)
@@ -807,5 +811,176 @@ func registerListRequirementArtifacts(srv *mcpsdk.Server, deps ServerDeps) {
})
}
// ===== create_project =====
type createProjectIn struct {
Slug string `json:"slug" jsonschema:"project slug (non-empty)"`
Name string `json:"name" jsonschema:"display name (non-empty)"`
Description string `json:"description,omitempty"`
Visibility string `json:"visibility,omitempty" jsonschema:"private|internal (default private)"`
}
type projectDetail struct {
Slug string `json:"slug"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Visibility string `json:"visibility"`
Archived bool `json:"archived"`
CreatedAt string `json:"created_at"`
}
type projectCreateOut struct {
OK bool `json:"ok"`
Project projectDetail `json:"project"`
}
func projectDetailFrom(p *project.Project) projectDetail {
return projectDetail{
Slug: p.Slug, Name: p.Name, Description: p.Description,
Visibility: string(p.Visibility), Archived: p.ArchivedAt != nil,
CreatedAt: p.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
}
}
// checkProjectScopeUnrestricted 用于 create_project:token 若被限定到具体
// project 列表,则不允许创建落在列表外的新项目(新项目 ID 创建前未知,
// 只能整体拒绝)。
func checkProjectScopeUnrestricted(ctx context.Context) error {
sess, ok := FromContext(ctx)
if !ok {
return errs.New(errs.CodeInternal, "mcp: AuthSession missing")
}
if sess.Scope.ProjectIDs != nil {
return errs.New(errs.CodeMcpTokenScopeViolation,
"token is restricted to specific projects; creating new projects is not allowed")
}
return nil
}
func registerCreateProject(srv *mcpsdk.Server, deps ServerDeps) {
mcpsdk.AddTool(srv,
&mcpsdk.Tool{Name: "create_project", Description: "Create a new project."},
func(ctx context.Context, _ *mcpsdk.CallToolRequest, in createProjectIn) (*mcpsdk.CallToolResult, projectCreateOut, error) {
if err := CheckToolFromCtx(ctx, "create_project"); err != nil {
return nil, projectCreateOut{}, err
}
if err := checkProjectScopeUnrestricted(ctx); err != nil {
return nil, projectCreateOut{}, err
}
c, err := deps.Caller.Resolve(ctx)
if err != nil {
return nil, projectCreateOut{}, err
}
p, err := deps.Projects.Create(ctx, c, project.CreateProjectInput{
Slug: in.Slug, Name: in.Name, Description: in.Description,
Visibility: project.Visibility(in.Visibility),
})
if err != nil {
return nil, projectCreateOut{}, err
}
return nil, projectCreateOut{OK: true, Project: projectDetailFrom(p)}, nil
})
}
// ===== update_project =====
type updateProjectIn struct {
ProjectSlug string `json:"project_slug" jsonschema:"required field"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Visibility string `json:"visibility,omitempty" jsonschema:"private|internal"`
}
func registerUpdateProject(srv *mcpsdk.Server, deps ServerDeps) {
mcpsdk.AddTool(srv,
&mcpsdk.Tool{Name: "update_project", Description: "Update a project (name/description/visibility)."},
func(ctx context.Context, _ *mcpsdk.CallToolRequest, in updateProjectIn) (*mcpsdk.CallToolResult, projectCreateOut, error) {
if err := CheckToolFromCtx(ctx, "update_project"); err != nil {
return nil, projectCreateOut{}, err
}
c, err := deps.Caller.Resolve(ctx)
if err != nil {
return nil, projectCreateOut{}, err
}
p, err := deps.Projects.Get(ctx, c, in.ProjectSlug)
if err != nil {
return nil, projectCreateOut{}, err
}
if err := CheckProjectFromCtx(ctx, p.ID); err != nil {
return nil, projectCreateOut{}, err
}
input := project.UpdateProjectInput{}
if in.Name != "" {
input.Name = &in.Name
}
if in.Description != "" {
input.Description = &in.Description
}
if in.Visibility != "" {
v := project.Visibility(in.Visibility)
input.Visibility = &v
}
p, err = deps.Projects.Update(ctx, c, in.ProjectSlug, input)
if err != nil {
return nil, projectCreateOut{}, err
}
return nil, projectCreateOut{OK: true, Project: projectDetailFrom(p)}, nil
})
}
// ===== archive/unarchive_project =====
type archiveProjectIn struct {
ProjectSlug string `json:"project_slug" jsonschema:"required field"`
}
func registerArchiveProject(srv *mcpsdk.Server, deps ServerDeps) {
mcpsdk.AddTool(srv,
&mcpsdk.Tool{Name: "archive_project", Description: "Archive a project (idempotent)."},
func(ctx context.Context, _ *mcpsdk.CallToolRequest, in archiveProjectIn) (*mcpsdk.CallToolResult, okOut, error) {
if err := CheckToolFromCtx(ctx, "archive_project"); err != nil {
return nil, okOut{}, err
}
c, err := deps.Caller.Resolve(ctx)
if err != nil {
return nil, okOut{}, err
}
p, err := deps.Projects.Get(ctx, c, in.ProjectSlug)
if err != nil {
return nil, okOut{}, err
}
if err := CheckProjectFromCtx(ctx, p.ID); err != nil {
return nil, okOut{}, err
}
if err := deps.Projects.Archive(ctx, c, in.ProjectSlug); err != nil {
return nil, okOut{}, err
}
return nil, okOut{OK: true}, nil
})
}
func registerUnarchiveProject(srv *mcpsdk.Server, deps ServerDeps) {
mcpsdk.AddTool(srv,
&mcpsdk.Tool{Name: "unarchive_project", Description: "Unarchive a project (idempotent)."},
func(ctx context.Context, _ *mcpsdk.CallToolRequest, in archiveProjectIn) (*mcpsdk.CallToolResult, okOut, error) {
if err := CheckToolFromCtx(ctx, "unarchive_project"); err != nil {
return nil, okOut{}, err
}
c, err := deps.Caller.Resolve(ctx)
if err != nil {
return nil, okOut{}, err
}
p, err := deps.Projects.Get(ctx, c, in.ProjectSlug)
if err != nil {
return nil, okOut{}, err
}
if err := CheckProjectFromCtx(ctx, p.ID); err != nil {
return nil, okOut{}, err
}
if err := deps.Projects.Unarchive(ctx, c, in.ProjectSlug); err != nil {
return nil, okOut{}, err
}
return nil, okOut{OK: true}, nil
})
}
// 保证 fmt 引用(编译期检查)
var _ = fmt.Sprintf