main
  1package git
  2
  3import (
  4	"fmt"
  5	"os"
  6	"os/exec"
  7	"path/filepath"
  8	"strconv"
  9	"strings"
 10
 11	"github.com/xlgmokha/mcp/pkg/mcp"
 12)
 13
 14// Commit represents a git commit
 15type Commit struct {
 16	Hash    string
 17	Message string
 18	Author  string
 19	Date    string
 20}
 21
 22// GitOperations provides git operations for a specific repository
 23type GitOperations struct {
 24	repoPath string
 25}
 26
 27// NewGitOperations creates a new GitOperations helper
 28func NewGitOperations(repoPath string) *GitOperations {
 29	return &GitOperations{repoPath: repoPath}
 30}
 31
 32// New creates a new Git MCP server
 33func New(repoPath string) *mcp.Server {
 34	git := NewGitOperations(repoPath)
 35	builder := mcp.NewServerBuilder("mcp-git", "1.0.0")
 36
 37	// Add git_status tool
 38	builder.AddTool(mcp.NewTool("git_status", "Shows the working tree status", map[string]interface{}{
 39		"type": "object",
 40		"properties": map[string]interface{}{
 41			"repo_path": map[string]interface{}{
 42				"type":        "string",
 43				"description": "Path to the Git repository",
 44			},
 45		},
 46		"required": []string{"repo_path"},
 47	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
 48		repoPath, ok := req.Arguments["repo_path"].(string)
 49		if !ok {
 50			repoPath = git.repoPath
 51		}
 52
 53		output, err := git.runGitCommand(repoPath, "status")
 54		if err != nil {
 55			return mcp.NewToolError(fmt.Sprintf("git status failed: %v", err)), nil
 56		}
 57
 58		return mcp.NewToolResult(mcp.NewTextContent(fmt.Sprintf("Repository status:\n%s", output))), nil
 59	}))
 60
 61	// Add git_diff_unstaged tool
 62	builder.AddTool(mcp.NewTool("git_diff_unstaged", "Shows changes in the working directory that are not yet staged", map[string]interface{}{
 63		"type": "object",
 64		"properties": map[string]interface{}{
 65			"repo_path": map[string]interface{}{
 66				"type":        "string",
 67				"description": "Path to the Git repository",
 68			},
 69		},
 70		"required": []string{"repo_path"},
 71	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
 72		repoPath, ok := req.Arguments["repo_path"].(string)
 73		if !ok {
 74			repoPath = git.repoPath
 75		}
 76
 77		output, err := git.runGitCommand(repoPath, "diff")
 78		if err != nil {
 79			return mcp.NewToolError(fmt.Sprintf("git diff failed: %v", err)), nil
 80		}
 81
 82		return mcp.NewToolResult(mcp.NewTextContent(fmt.Sprintf("Unstaged changes:\n%s", output))), nil
 83	}))
 84
 85	// Add git_diff_staged tool
 86	builder.AddTool(mcp.NewTool("git_diff_staged", "Shows changes that are staged for commit", map[string]interface{}{
 87		"type": "object",
 88		"properties": map[string]interface{}{
 89			"repo_path": map[string]interface{}{
 90				"type":        "string",
 91				"description": "Path to the Git repository",
 92			},
 93		},
 94		"required": []string{"repo_path"},
 95	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
 96		repoPath, ok := req.Arguments["repo_path"].(string)
 97		if !ok {
 98			repoPath = git.repoPath
 99		}
100
101		output, err := git.runGitCommand(repoPath, "diff", "--cached")
102		if err != nil {
103			return mcp.NewToolError(fmt.Sprintf("git diff --cached failed: %v", err)), nil
104		}
105
106		return mcp.NewToolResult(mcp.NewTextContent(fmt.Sprintf("Staged changes:\n%s", output))), nil
107	}))
108
109	// Add git_diff tool
110	builder.AddTool(mcp.NewTool("git_diff", "Shows differences between branches or commits", map[string]interface{}{
111		"type": "object",
112		"properties": map[string]interface{}{
113			"repo_path": map[string]interface{}{
114				"type":        "string",
115				"description": "Path to the Git repository",
116			},
117			"target": map[string]interface{}{
118				"type":        "string",
119				"description": "Target branch or commit to diff against",
120			},
121		},
122		"required": []string{"repo_path", "target"},
123	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
124		repoPath, ok := req.Arguments["repo_path"].(string)
125		if !ok {
126			repoPath = git.repoPath
127		}
128
129		target, ok := req.Arguments["target"].(string)
130		if !ok {
131			return mcp.NewToolError("target is required"), nil
132		}
133
134		output, err := git.runGitCommand(repoPath, "diff", target)
135		if err != nil {
136			return mcp.NewToolError(fmt.Sprintf("git diff failed: %v", err)), nil
137		}
138
139		return mcp.NewToolResult(mcp.NewTextContent(fmt.Sprintf("Diff with %s:\n%s", target, output))), nil
140	}))
141
142	// Add git_commit tool
143	builder.AddTool(mcp.NewTool("git_commit", "Records changes to the repository", map[string]interface{}{
144		"type": "object",
145		"properties": map[string]interface{}{
146			"repo_path": map[string]interface{}{
147				"type":        "string",
148				"description": "Path to the Git repository",
149			},
150			"message": map[string]interface{}{
151				"type":        "string",
152				"description": "Commit message",
153			},
154		},
155		"required": []string{"repo_path", "message"},
156	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
157		repoPath, ok := req.Arguments["repo_path"].(string)
158		if !ok {
159			repoPath = git.repoPath
160		}
161
162		message, ok := req.Arguments["message"].(string)
163		if !ok {
164			return mcp.NewToolError("message is required"), nil
165		}
166
167		output, err := git.runGitCommand(repoPath, "commit", "-m", message)
168		if err != nil {
169			return mcp.NewToolError(fmt.Sprintf("git commit failed: %v", err)), nil
170		}
171
172		return mcp.NewToolResult(mcp.NewTextContent(fmt.Sprintf("Changes committed successfully:\n%s", output))), nil
173	}))
174
175	// Add git_add tool
176	builder.AddTool(mcp.NewTool("git_add", "Adds file contents to the staging area", map[string]interface{}{
177		"type": "object",
178		"properties": map[string]interface{}{
179			"repo_path": map[string]interface{}{
180				"type":        "string",
181				"description": "Path to the Git repository",
182			},
183			"files": map[string]interface{}{
184				"type": "array",
185				"items": map[string]interface{}{
186					"type": "string",
187				},
188				"description": "List of files to add",
189			},
190		},
191		"required": []string{"repo_path", "files"},
192	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
193		repoPath, ok := req.Arguments["repo_path"].(string)
194		if !ok {
195			repoPath = git.repoPath
196		}
197
198		filesInterface, ok := req.Arguments["files"]
199		if !ok {
200			return mcp.NewToolError("files is required"), nil
201		}
202
203		files, err := convertToStringSlice(filesInterface)
204		if err != nil {
205			return mcp.NewToolError("files must be an array of strings"), nil
206		}
207
208		args := append([]string{"add"}, files...)
209		_, err = git.runGitCommand(repoPath, args...)
210		if err != nil {
211			return mcp.NewToolError(fmt.Sprintf("git add failed: %v", err)), nil
212		}
213
214		return mcp.NewToolResult(mcp.NewTextContent("Files staged successfully")), nil
215	}))
216
217	// Add git_reset tool
218	builder.AddTool(mcp.NewTool("git_reset", "Unstages all staged changes", map[string]interface{}{
219		"type": "object",
220		"properties": map[string]interface{}{
221			"repo_path": map[string]interface{}{
222				"type":        "string",
223				"description": "Path to the Git repository",
224			},
225		},
226		"required": []string{"repo_path"},
227	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
228		repoPath, ok := req.Arguments["repo_path"].(string)
229		if !ok {
230			repoPath = git.repoPath
231		}
232
233		_, err := git.runGitCommand(repoPath, "reset")
234		if err != nil {
235			return mcp.NewToolError(fmt.Sprintf("git reset failed: %v", err)), nil
236		}
237
238		return mcp.NewToolResult(mcp.NewTextContent("All staged changes reset")), nil
239	}))
240
241	// Add git_log tool
242	builder.AddTool(mcp.NewTool("git_log", "Shows the commit logs", map[string]interface{}{
243		"type": "object",
244		"properties": map[string]interface{}{
245			"repo_path": map[string]interface{}{
246				"type":        "string",
247				"description": "Path to the Git repository",
248			},
249			"max_count": map[string]interface{}{
250				"type":        "integer",
251				"description": "Maximum number of commits to show",
252				"default":     10,
253			},
254		},
255		"required": []string{"repo_path"},
256	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
257		repoPath, ok := req.Arguments["repo_path"].(string)
258		if !ok {
259			repoPath = git.repoPath
260		}
261
262		maxCount := 10
263		if mc, exists := req.Arguments["max_count"]; exists {
264			if count, ok := mc.(float64); ok {
265				maxCount = int(count)
266			}
267		}
268
269		output, err := git.runGitCommand(repoPath, "log", "--oneline", "-n", strconv.Itoa(maxCount))
270		if err != nil {
271			return mcp.NewToolError(fmt.Sprintf("git log failed: %v", err)), nil
272		}
273
274		return mcp.NewToolResult(mcp.NewTextContent(fmt.Sprintf("Commit history:\n%s", output))), nil
275	}))
276
277	// Add git_create_branch tool
278	builder.AddTool(mcp.NewTool("git_create_branch", "Creates a new branch from an optional base branch", map[string]interface{}{
279		"type": "object",
280		"properties": map[string]interface{}{
281			"repo_path": map[string]interface{}{
282				"type":        "string",
283				"description": "Path to the Git repository",
284			},
285			"branch_name": map[string]interface{}{
286				"type":        "string",
287				"description": "Name of the new branch",
288			},
289			"base_branch": map[string]interface{}{
290				"type":        "string",
291				"description": "Base branch to create from (optional)",
292			},
293		},
294		"required": []string{"repo_path", "branch_name"},
295	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
296		repoPath, ok := req.Arguments["repo_path"].(string)
297		if !ok {
298			repoPath = git.repoPath
299		}
300
301		branchName, ok := req.Arguments["branch_name"].(string)
302		if !ok {
303			return mcp.NewToolError("branch_name is required"), nil
304		}
305
306		baseBranch, _ := req.Arguments["base_branch"].(string)
307
308		var args []string
309		if baseBranch != "" {
310			args = []string{"checkout", "-b", branchName, baseBranch}
311		} else {
312			args = []string{"checkout", "-b", branchName}
313		}
314
315		_, err := git.runGitCommand(repoPath, args...)
316		if err != nil {
317			return mcp.NewToolError(fmt.Sprintf("git create branch failed: %v", err)), nil
318		}
319
320		return mcp.NewToolResult(mcp.NewTextContent(fmt.Sprintf("Created branch '%s'", branchName))), nil
321	}))
322
323	// Add git_checkout tool
324	builder.AddTool(mcp.NewTool("git_checkout", "Switches branches", map[string]interface{}{
325		"type": "object",
326		"properties": map[string]interface{}{
327			"repo_path": map[string]interface{}{
328				"type":        "string",
329				"description": "Path to the Git repository",
330			},
331			"branch_name": map[string]interface{}{
332				"type":        "string",
333				"description": "Name of the branch to checkout",
334			},
335		},
336		"required": []string{"repo_path", "branch_name"},
337	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
338		repoPath, ok := req.Arguments["repo_path"].(string)
339		if !ok {
340			repoPath = git.repoPath
341		}
342
343		branchName, ok := req.Arguments["branch_name"].(string)
344		if !ok {
345			return mcp.NewToolError("branch_name is required"), nil
346		}
347
348		_, err := git.runGitCommand(repoPath, "checkout", branchName)
349		if err != nil {
350			return mcp.NewToolError(fmt.Sprintf("git checkout failed: %v", err)), nil
351		}
352
353		return mcp.NewToolResult(mcp.NewTextContent(fmt.Sprintf("Switched to branch '%s'", branchName))), nil
354	}))
355
356	// Add git_show tool
357	builder.AddTool(mcp.NewTool("git_show", "Shows the contents of a commit", map[string]interface{}{
358		"type": "object",
359		"properties": map[string]interface{}{
360			"repo_path": map[string]interface{}{
361				"type":        "string",
362				"description": "Path to the Git repository",
363			},
364			"revision": map[string]interface{}{
365				"type":        "string",
366				"description": "Commit hash or reference to show",
367			},
368		},
369		"required": []string{"repo_path", "revision"},
370	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
371		repoPath, ok := req.Arguments["repo_path"].(string)
372		if !ok {
373			repoPath = git.repoPath
374		}
375
376		revision, ok := req.Arguments["revision"].(string)
377		if !ok {
378			return mcp.NewToolError("revision is required"), nil
379		}
380
381		output, err := git.runGitCommand(repoPath, "show", revision)
382		if err != nil {
383			return mcp.NewToolError(fmt.Sprintf("git show failed: %v", err)), nil
384		}
385
386		return mcp.NewToolResult(mcp.NewTextContent(output)), nil
387	}))
388
389	// Add git_init tool
390	builder.AddTool(mcp.NewTool("git_init", "Initialize a new Git repository", map[string]interface{}{
391		"type": "object",
392		"properties": map[string]interface{}{
393			"repo_path": map[string]interface{}{
394				"type":        "string",
395				"description": "Path where to initialize the Git repository",
396			},
397		},
398		"required": []string{"repo_path"},
399	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
400		repoPath, ok := req.Arguments["repo_path"].(string)
401		if !ok {
402			repoPath = git.repoPath
403		}
404
405		if err := os.MkdirAll(repoPath, 0755); err != nil {
406			return mcp.NewToolError(fmt.Sprintf("failed to create directory: %v", err)), nil
407		}
408
409		_, err := git.runGitCommand(repoPath, "init")
410		if err != nil {
411			return mcp.NewToolError(fmt.Sprintf("git init failed: %v", err)), nil
412		}
413
414		return mcp.NewToolResult(mcp.NewTextContent(fmt.Sprintf("Initialized empty Git repository in %s", repoPath))), nil
415	}))
416
417	// Add commit-message prompt
418	builder.AddPrompt(mcp.NewPrompt("commit-message", "Prompt for crafting a well-structured git commit message", []mcp.PromptArgument{
419		{
420			Name:        "changes",
421			Description: "Description of the changes being committed",
422			Required:    true,
423		},
424		{
425			Name:        "type",
426			Description: "Type of change (feat, fix, docs, style, refactor, test, chore)",
427			Required:    false,
428		},
429		{
430			Name:        "breaking",
431			Description: "Whether this is a breaking change (true/false)",
432			Required:    false,
433		},
434	}, func(req mcp.GetPromptRequest) (mcp.GetPromptResult, error) {
435		changes, hasChanges := req.Arguments["changes"].(string)
436		commitType, hasType := req.Arguments["type"].(string)
437		breaking, hasBreaking := req.Arguments["breaking"]
438
439		if !hasChanges || changes == "" {
440			return mcp.GetPromptResult{}, fmt.Errorf("changes argument is required")
441		}
442
443		if !hasType || commitType == "" {
444			commitType = "feat"
445		}
446
447		isBreaking := false
448		if hasBreaking {
449			if breakingBool, ok := breaking.(bool); ok {
450				isBreaking = breakingBool
451			} else if breakingStr, ok := breaking.(string); ok {
452				isBreaking = breakingStr == "true"
453			}
454		}
455
456		var messages []mcp.PromptMessage
457
458		userContent := fmt.Sprintf(`I need help writing a git commit message for the following changes:
459
460%s
461
462Please help me craft a well-structured commit message following conventional commit format.`, changes)
463
464		if hasType {
465			userContent += fmt.Sprintf("\n\nCommit type: %s", commitType)
466		}
467		if isBreaking {
468			userContent += "\n\nThis is a BREAKING CHANGE."
469		}
470
471		messages = append(messages, mcp.PromptMessage{
472			Role:    "user",
473			Content: mcp.NewTextContent(userContent),
474		})
475
476		breakingPrefix := ""
477		if isBreaking {
478			breakingPrefix = "!"
479		}
480
481		assistantContent := fmt.Sprintf(`I'll help you create a conventional commit message. Here's the suggested format:
482
483**Commit message:**
484%s%s: %s
485
486**Format explanation:**
487- Type: %s (indicates the nature of the change)
488- Description: Clear, concise summary in present tense
489%s
490
491**Additional guidelines:**
492- Keep the subject line under 50 characters
493- Use imperative mood ("add" not "added")
494- Don't end subject line with a period
495- Include body if needed to explain what and why`,
496			commitType, breakingPrefix, changes,
497			commitType,
498			func() string {
499				if isBreaking {
500					return "- Breaking change: This change breaks backward compatibility"
501				}
502				return ""
503			}())
504
505		messages = append(messages, mcp.PromptMessage{
506			Role:    "assistant",
507			Content: mcp.NewTextContent(assistantContent),
508		})
509
510		description := fmt.Sprintf("Commit message guidance for %s changes", commitType)
511		if isBreaking {
512			description += " (BREAKING)"
513		}
514
515		return mcp.GetPromptResult{
516			Description: description,
517			Messages:    messages,
518		}, nil
519	}))
520
521	// Add git:// pattern resource for dynamic file access
522	builder.AddResource(mcp.NewResource(
523		"git://",
524		"Git Repository",
525		"",
526		func(req mcp.ReadResourceRequest) (mcp.ReadResourceResult, error) {
527			return git.handleGitResource(req)
528		},
529	))
530
531	// Add repository root if it's a git repository
532	gitDir := filepath.Join(git.repoPath, ".git")
533	if _, err := os.Stat(gitDir); err == nil {
534		currentBranch, err := git.getCurrentBranch()
535		if err != nil {
536			currentBranch = "unknown"
537		}
538
539		gitURI := "git://" + git.repoPath
540		repoName := filepath.Base(git.repoPath)
541		if repoName == "." || repoName == "/" {
542			repoName = git.repoPath
543		}
544
545		rootName := fmt.Sprintf("Git Repository: %s (branch: %s)", repoName, currentBranch)
546		builder.AddRoot(mcp.NewRoot(gitURI, rootName))
547	}
548
549	return builder.Build()
550}
551
552// Helper methods for GitOperations
553
554func (git *GitOperations) runGitCommand(repoPath string, args ...string) (string, error) {
555	if _, err := os.Stat(repoPath); os.IsNotExist(err) {
556		return "", fmt.Errorf("repository path does not exist: %s", repoPath)
557	}
558
559	if len(args) > 0 && args[0] != "init" {
560		gitDir := filepath.Join(repoPath, ".git")
561		if _, err := os.Stat(gitDir); os.IsNotExist(err) {
562			return "", fmt.Errorf("not a git repository: %s", repoPath)
563		}
564	}
565
566	cmd := exec.Command("git", args...)
567	cmd.Dir = repoPath
568
569	output, err := cmd.CombinedOutput()
570	if err != nil {
571		return "", fmt.Errorf("%v: %s", err, string(output))
572	}
573
574	return strings.TrimSpace(string(output)), nil
575}
576
577func (git *GitOperations) getCurrentBranch() (string, error) {
578	output, err := git.runGitCommand(git.repoPath, "branch", "--show-current")
579	if err != nil {
580		return "", err
581	}
582	branch := strings.TrimSpace(output)
583	if branch == "" {
584		branch = "HEAD"
585	}
586	return branch, nil
587}
588
589func (git *GitOperations) getTrackedFiles() ([]string, error) {
590	output, err := git.runGitCommand(git.repoPath, "ls-files")
591	if err != nil {
592		return nil, err
593	}
594
595	if output == "" {
596		return []string{}, nil
597	}
598
599	files := strings.Split(output, "\n")
600	var filteredFiles []string
601
602	for _, file := range files {
603		file = strings.TrimSpace(file)
604		if file != "" && !strings.HasPrefix(file, ".") {
605			filteredFiles = append(filteredFiles, file)
606		}
607	}
608
609	return filteredFiles, nil
610}
611
612func (git *GitOperations) getBranches() ([]string, error) {
613	output, err := git.runGitCommand(git.repoPath, "branch", "--format=%(refname:short)")
614	if err != nil {
615		return nil, err
616	}
617
618	if output == "" {
619		return []string{}, nil
620	}
621
622	branches := strings.Split(output, "\n")
623	var filteredBranches []string
624
625	for _, branch := range branches {
626		branch = strings.TrimSpace(branch)
627		if branch != "" {
628			filteredBranches = append(filteredBranches, branch)
629		}
630	}
631
632	return filteredBranches, nil
633}
634
635func (git *GitOperations) getRecentCommits(count int) ([]Commit, error) {
636	output, err := git.runGitCommand(git.repoPath, "log", "--format=%H|%s|%an|%ad", "--date=short", "-n", strconv.Itoa(count))
637	if err != nil {
638		return nil, err
639	}
640
641	if output == "" {
642		return []Commit{}, nil
643	}
644
645	lines := strings.Split(output, "\n")
646	var commits []Commit
647
648	for _, line := range lines {
649		line = strings.TrimSpace(line)
650		if line == "" {
651			continue
652		}
653
654		parts := strings.Split(line, "|")
655		if len(parts) >= 4 {
656			commits = append(commits, Commit{
657				Hash:    parts[0],
658				Message: parts[1],
659				Author:  parts[2],
660				Date:    parts[3],
661			})
662		}
663	}
664
665	return commits, nil
666}
667
668func (git *GitOperations) handleGitResource(req mcp.ReadResourceRequest) (mcp.ReadResourceResult, error) {
669	if !strings.HasPrefix(req.URI, "git://") {
670		return mcp.ReadResourceResult{}, fmt.Errorf("invalid git URI: %s", req.URI)
671	}
672
673	uriPath := req.URI[6:]
674	parts := strings.Split(uriPath, "/")
675
676	if len(parts) < 3 {
677		return mcp.ReadResourceResult{}, fmt.Errorf("invalid git URI format: %s", req.URI)
678	}
679
680	repoPath := parts[0]
681	resourceType := parts[1]
682	resourcePath := strings.Join(parts[2:], "/")
683
684	if repoPath != git.repoPath {
685		return mcp.ReadResourceResult{}, fmt.Errorf("access denied: repository path mismatch")
686	}
687
688	switch resourceType {
689	case "branch":
690		return git.handleBranchResource(resourcePath)
691	case "commit":
692		return git.handleCommitResource(resourcePath)
693	default:
694		return git.handleFileResource(resourceType, resourcePath)
695	}
696}
697
698func (git *GitOperations) handleFileResource(branch, filePath string) (mcp.ReadResourceResult, error) {
699	gitPath := fmt.Sprintf("%s:%s", branch, filePath)
700	output, err := git.runGitCommand(git.repoPath, "show", gitPath)
701	if err != nil {
702		return mcp.ReadResourceResult{}, fmt.Errorf("failed to read git file: %v", err)
703	}
704
705	return mcp.ReadResourceResult{
706		Contents: []mcp.Content{
707			mcp.NewTextContent(output),
708		},
709	}, nil
710}
711
712func (git *GitOperations) handleBranchResource(branchName string) (mcp.ReadResourceResult, error) {
713	output, err := git.runGitCommand(git.repoPath, "log", "--oneline", "-n", "5", branchName)
714	if err != nil {
715		return mcp.ReadResourceResult{}, fmt.Errorf("failed to get branch info: %v", err)
716	}
717
718	content := fmt.Sprintf("Branch: %s\n\nRecent commits:\n%s", branchName, output)
719
720	return mcp.ReadResourceResult{
721		Contents: []mcp.Content{
722			mcp.NewTextContent(content),
723		},
724	}, nil
725}
726
727func (git *GitOperations) handleCommitResource(commitHash string) (mcp.ReadResourceResult, error) {
728	output, err := git.runGitCommand(git.repoPath, "show", "--stat", commitHash)
729	if err != nil {
730		return mcp.ReadResourceResult{}, fmt.Errorf("failed to get commit info: %v", err)
731	}
732
733	return mcp.ReadResourceResult{
734		Contents: []mcp.Content{
735			mcp.NewTextContent(output),
736		},
737	}, nil
738}
739
740// Helper function to convert interface{} to []string
741func convertToStringSlice(input interface{}) ([]string, error) {
742	switch v := input.(type) {
743	case []interface{}:
744		result := make([]string, len(v))
745		for i, item := range v {
746			str, ok := item.(string)
747			if !ok {
748				return nil, fmt.Errorf("item at index %d is not a string", i)
749			}
750			result[i] = str
751		}
752		return result, nil
753	case []string:
754		return v, nil
755	default:
756		return nil, fmt.Errorf("input is not a slice")
757	}
758}
759
760// Helper function to determine MIME type for git files
761func getGitMimeType(filePath string) string {
762	ext := strings.ToLower(filepath.Ext(filePath))
763	switch ext {
764	case ".go":
765		return "text/x-go"
766	case ".js":
767		return "text/javascript"
768	case ".ts":
769		return "text/typescript"
770	case ".py":
771		return "text/x-python"
772	case ".md":
773		return "text/markdown"
774	case ".json":
775		return "application/json"
776	case ".yaml", ".yml":
777		return "application/x-yaml"
778	case ".xml":
779		return "application/xml"
780	case ".html", ".htm":
781		return "text/html"
782	case ".css":
783		return "text/css"
784	case ".txt":
785		return "text/plain"
786	default:
787		return "text/plain"
788	}
789}