chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:37:57 +08:00
commit e30f8ba47c
533 changed files with 115926 additions and 0 deletions
+411
View File
@@ -0,0 +1,411 @@
package github
import (
"bytes"
"context"
"encoding/csv"
"encoding/json"
"fmt"
"sort"
"strings"
"github.com/github/github-mcp-server/pkg/inventory"
"github.com/github/github-mcp-server/pkg/utils"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// Ordered by preference when a response wrapper contains multiple arrays.
var primaryCSVRowKeys = []string{
"items",
"issues",
"discussions",
"categories",
"labels",
"alerts",
"advisories",
"notifications",
"gists",
"repositories",
"commits",
"branches",
"tags",
"releases",
"users",
"teams",
"members",
"projects",
"nodes",
}
type csvOutputDocument struct {
metadata map[string]string
rows []map[string]string
}
// withCSVOutput wraps the handler of every default-toolset list_* tool so that,
// at request time, it checks the csv_output feature flag and converts the JSON
// text response to CSV when enabled. The tool's schema, name, and scope are
// unchanged — only the response payload format differs.
func withCSVOutput(tools []inventory.ServerTool) []inventory.ServerTool {
for i := range tools {
if !isCSVOutputTool(tools[i]) {
continue
}
tools[i].HandlerFunc = wrapHandlerWithCSVOutput(tools[i].HandlerFunc)
}
return tools
}
// isCSVOutputTool reports whether the given tool should have its handler
// wrapped to honor the csv_output feature flag. Wrapping happens at slice
// construction time, before the per-request feature-flag filter chooses which
// variant of a flag-gated tool to register, so flag-gated list_* tools are
// included on equal footing — only the live variant ever runs at request time.
func isCSVOutputTool(tool inventory.ServerTool) bool {
if !tool.Toolset.Default {
return false
}
return strings.HasPrefix(tool.Tool.Name, "list_")
}
func wrapHandlerWithCSVOutput(next inventory.HandlerFunc) inventory.HandlerFunc {
return func(deps any) mcp.ToolHandler {
handler := next(deps)
csvDeps, _ := deps.(ToolDependencies)
return func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
result, err := handler(ctx, req)
if err != nil || result == nil || result.IsError {
return result, err
}
if csvDeps == nil || !csvDeps.IsFeatureEnabled(ctx, FeatureFlagCSVOutput) {
return result, nil
}
return convertJSONTextResultToCSV(result), nil
}
}
}
func convertJSONTextResultToCSV(result *mcp.CallToolResult) *mcp.CallToolResult {
if len(result.Content) != 1 {
return utils.NewToolResultError("failed to convert response to CSV: expected a single text content response")
}
text, ok := result.Content[0].(*mcp.TextContent)
if !ok {
return utils.NewToolResultError("failed to convert response to CSV: expected a text content response")
}
csvText, err := jsonTextToCSV(text.Text)
if err != nil {
return utils.NewToolResultErrorFromErr("failed to convert response to CSV", err)
}
result.Content = []mcp.Content{&mcp.TextContent{Text: csvText}}
result.StructuredContent = nil
return result
}
func jsonTextToCSV(text string) (string, error) {
decoder := json.NewDecoder(strings.NewReader(text))
decoder.UseNumber()
var value any
if err := decoder.Decode(&value); err != nil {
return "", fmt.Errorf("failed to unmarshal JSON text: %w", err)
}
doc := csvDocument(value)
if len(doc.metadata) == 0 && len(doc.rows) == 0 {
return "", nil
}
var buf bytes.Buffer
writeCSVMetadata(&buf, doc.metadata)
if len(doc.rows) == 0 {
return buf.String(), nil
}
headers := csvHeaders(doc.rows)
if len(headers) == 0 {
return buf.String(), nil
}
writer := csv.NewWriter(&buf)
if err := writer.Write(headers); err != nil {
return "", fmt.Errorf("failed to write CSV header: %w", err)
}
for _, row := range doc.rows {
record := make([]string, len(headers))
for i, header := range headers {
record[i] = row[header]
}
if err := writer.Write(record); err != nil {
return "", fmt.Errorf("failed to write CSV row: %w", err)
}
}
writer.Flush()
if err := writer.Error(); err != nil {
return "", fmt.Errorf("failed to flush CSV: %w", err)
}
return buf.String(), nil
}
func csvDocument(value any) csvOutputDocument {
switch v := value.(type) {
case []any:
return csvOutputDocument{rows: csvRowsFromArray(v)}
case map[string]any:
if rows, metadata, ok := primaryRowsFromMap(v); ok {
return csvOutputDocument{
metadata: newFlattenedCSVRow(metadata),
rows: csvRowsFromArray(rows),
}
}
return csvOutputDocument{rows: []map[string]string{newFlattenedCSVRow(v)}}
default:
return csvOutputDocument{rows: []map[string]string{scalarCSVRow(v)}}
}
}
func primaryRowsFromMap(value map[string]any) ([]any, map[string]any, bool) {
if rows, path, ok := primaryRowsAtCurrentLevel(value); ok {
return rows, metadataWithoutPath(value, path), true
}
if rows, path, ok := primaryRowsOneLevelDown(value); ok {
return rows, metadataWithoutPath(value, path), true
}
return nil, nil, false
}
func primaryRowsAtCurrentLevel(value map[string]any) ([]any, []string, bool) {
if key, ok := preferredPrimaryRowKey(value); ok {
rows, _ := value[key].([]any)
return rows, []string{key}, true
}
if key, ok := singleArrayKey(value); ok {
rows, _ := value[key].([]any)
return rows, []string{key}, true
}
return nil, nil, false
}
func primaryRowsOneLevelDown(value map[string]any) ([]any, []string, bool) {
var matchedRows []any
var matchedPath []string
for key, raw := range value {
child, ok := raw.(map[string]any)
if !ok {
continue
}
rows, path, ok := primaryRowsAtCurrentLevel(child)
if !ok {
continue
}
if matchedPath != nil {
return nil, nil, false
}
matchedRows = rows
matchedPath = append([]string{key}, path...)
}
if matchedPath == nil {
return nil, nil, false
}
return matchedRows, matchedPath, true
}
func metadataWithoutPath(value map[string]any, path []string) map[string]any {
metadata := make(map[string]any, len(value))
for key, raw := range value {
if key != path[0] {
metadata[key] = raw
continue
}
if len(path) == 1 {
continue
}
child, ok := raw.(map[string]any)
if !ok {
continue
}
childMetadata := metadataWithoutPath(child, path[1:])
if len(childMetadata) > 0 {
metadata[key] = childMetadata
}
}
return metadata
}
func csvRowsFromArray(values []any) []map[string]string {
if len(values) == 0 {
return nil
}
rows := make([]map[string]string, 0, len(values))
for _, value := range values {
var row map[string]string
switch v := value.(type) {
case map[string]any:
row = make(map[string]string)
appendFlattenedCSVFields(row, v, "")
default:
row = scalarCSVRow(v)
}
rows = append(rows, row)
}
return rows
}
func writeCSVMetadata(buf *bytes.Buffer, metadata map[string]string) {
if len(metadata) == 0 {
return
}
headers := make([]string, 0, len(metadata))
for header := range metadata {
headers = append(headers, header)
}
sort.Strings(headers)
for _, header := range headers {
fmt.Fprintf(buf, "# %s: %s\n", header, normalizeCSVWhitespace(metadata[header]))
}
buf.WriteByte('\n')
}
func newFlattenedCSVRow(value map[string]any) map[string]string {
row := make(map[string]string)
appendFlattenedCSVFields(row, value, "")
return row
}
func appendFlattenedCSVFields(row map[string]string, value map[string]any, prefix string) {
if value == nil {
return
}
for key, raw := range value {
column := csvColumnName(prefix, key)
switch v := raw.(type) {
case map[string]any:
appendFlattenedCSVFields(row, v, column)
case []any:
row[column] = csvArrayValue(v)
default:
row[column] = csvColumnValue(column, v)
}
}
}
func csvHeaders(rows []map[string]string) []string {
headerSet := make(map[string]struct{})
for _, row := range rows {
for header := range row {
headerSet[header] = struct{}{}
}
}
headers := make([]string, 0, len(headerSet))
for header := range headerSet {
headers = append(headers, header)
}
sort.Strings(headers)
return headers
}
func csvColumnName(prefix, key string) string {
if prefix == "" {
return key
}
return prefix + "." + key
}
func preferredPrimaryRowKey(value map[string]any) (string, bool) {
for _, key := range primaryCSVRowKeys {
if _, ok := value[key].([]any); ok {
return key, true
}
}
return "", false
}
func singleArrayKey(value map[string]any) (string, bool) {
var arrayKey string
for key, raw := range value {
if _, ok := raw.([]any); !ok {
continue
}
if arrayKey != "" {
return "", false
}
arrayKey = key
}
if arrayKey == "" {
return "", false
}
return arrayKey, true
}
func csvColumnValue(column string, value any) string {
str := scalarCSVValue(value)
if isBodyColumn(column) {
return normalizeCSVWhitespace(str)
}
return str
}
func csvArrayValue(values []any) string {
if len(values) == 0 {
return ""
}
// Scalar arrays use semicolons for compactness. This is lossy if an
// element contains a semicolon; use JSON mode when exact reconstruction matters.
parts := make([]string, 0, len(values))
for _, value := range values {
switch value.(type) {
case map[string]any, []any:
encoded, err := json.Marshal(value)
if err != nil {
parts = append(parts, scalarCSVValue(value))
} else {
parts = append(parts, string(encoded))
}
default:
parts = append(parts, scalarCSVValue(value))
}
}
return strings.Join(parts, ";")
}
func scalarCSVRow(value any) map[string]string {
return map[string]string{"value": scalarCSVValue(value)}
}
func scalarCSVValue(value any) string {
switch v := value.(type) {
case nil:
return ""
case string:
return v
case json.Number:
return v.String()
case bool:
if v {
return "true"
}
return "false"
default:
return fmt.Sprint(v)
}
}
func isBodyColumn(column string) bool {
return column == "body" || strings.HasSuffix(column, ".body")
}
func normalizeCSVWhitespace(value string) string {
return strings.Join(strings.Fields(value), " ")
}