2
0

feat(mcp): add list_secrets tool for MCP API

Implements list_secrets MCP tool to query available secrets across global, organization, and repository scopes. Returns secret names, descriptions, and metadata without exposing values. Supports optional owner and repo parameters to filter by scope.
This commit is contained in:
2026-01-24 15:01:58 -05:00
parent db8f606a5c
commit 394dca290c

View File

@@ -14,6 +14,8 @@ import (
actions_model "code.gitcaddy.com/server/v3/models/actions"
"code.gitcaddy.com/server/v3/models/db"
repo_model "code.gitcaddy.com/server/v3/models/repo"
secret_model "code.gitcaddy.com/server/v3/models/secret"
user_model "code.gitcaddy.com/server/v3/models/user"
"code.gitcaddy.com/server/v3/modules/actions"
"code.gitcaddy.com/server/v3/modules/json"
"code.gitcaddy.com/server/v3/modules/log"
@@ -235,6 +237,23 @@ var mcpTools = []MCPTool{
"required": []string{"owner", "repo", "tag"},
},
},
{
Name: "list_secrets",
Description: "List available secrets (names and descriptions only, not values) for workflows. Shows global, organization, and repository secrets.",
InputSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"owner": map[string]any{
"type": "string",
"description": "Repository or organization owner (optional, shows global secrets if omitted)",
},
"repo": map[string]any{
"type": "string",
"description": "Repository name (optional, shows org secrets if omitted)",
},
},
},
},
}
// MCPHandler handles MCP protocol requests
@@ -326,6 +345,8 @@ func handleToolsCall(ctx *context.APIContext, req *MCPRequest) {
result, err = toolListReleases(ctx, params.Arguments)
case "get_release":
result, err = toolGetRelease(ctx, params.Arguments)
case "list_secrets":
result, err = toolListSecrets(ctx, params.Arguments)
case "get_error_patterns":
result, err = toolGetErrorPatterns(ctx, params.Arguments)
case "report_error_solution":
@@ -789,3 +810,94 @@ func toolGetRelease(ctx *context.APIContext, args map[string]any) (any, error) {
"asset_count": len(assets),
}, nil
}
func toolListSecrets(ctx *context.APIContext, args map[string]any) (any, error) {
owner, _ := args["owner"].(string)
repo, _ := args["repo"].(string)
result := map[string]any{
"global_secrets": []map[string]any{},
"owner_secrets": []map[string]any{},
"repo_secrets": []map[string]any{},
}
// Always include global secrets (available to all workflows)
globalSecrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{Global: true})
if err != nil {
log.Error("Failed to fetch global secrets: %v", err)
} else {
globalList := make([]map[string]any, 0, len(globalSecrets))
for _, s := range globalSecrets {
globalList = append(globalList, map[string]any{
"name": s.Name,
"description": s.Description,
"created_at": s.CreatedUnix.AsTime().Format(time.RFC3339),
"scope": "global",
})
}
result["global_secrets"] = globalList
}
// If owner is specified, get org/user secrets
if owner != "" {
ownerUser, err := user_model.GetUserByName(ctx, owner)
if err != nil {
return nil, fmt.Errorf("owner not found: %s", owner)
}
ownerSecrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{OwnerID: ownerUser.ID})
if err != nil {
log.Error("Failed to fetch owner secrets: %v", err)
} else {
ownerList := make([]map[string]any, 0, len(ownerSecrets))
for _, s := range ownerSecrets {
scope := "user"
if ownerUser.IsOrganization() {
scope = "organization"
}
ownerList = append(ownerList, map[string]any{
"name": s.Name,
"description": s.Description,
"created_at": s.CreatedUnix.AsTime().Format(time.RFC3339),
"scope": scope,
"owner": owner,
})
}
result["owner_secrets"] = ownerList
}
// If repo is also specified, get repo secrets
if repo != "" {
repository, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner, repo)
if err != nil {
return nil, fmt.Errorf("repository not found: %s/%s", owner, repo)
}
repoSecrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{RepoID: repository.ID})
if err != nil {
log.Error("Failed to fetch repo secrets: %v", err)
} else {
repoList := make([]map[string]any, 0, len(repoSecrets))
for _, s := range repoSecrets {
repoList = append(repoList, map[string]any{
"name": s.Name,
"description": s.Description,
"created_at": s.CreatedUnix.AsTime().Format(time.RFC3339),
"scope": "repository",
"repo": fmt.Sprintf("%s/%s", owner, repo),
})
}
result["repo_secrets"] = repoList
}
}
}
// Add summary counts
result["total_count"] = len(result["global_secrets"].([]map[string]any)) +
len(result["owner_secrets"].([]map[string]any)) +
len(result["repo_secrets"].([]map[string]any))
result["note"] = "Secret values are not shown for security. Only names and descriptions are available."
return result, nil
}