Files
search-mcp-server/grep.go
T

206 lines
4.7 KiB
Go

package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
type GrepAppClient struct {
BaseURL string
HTTPClient *http.Client
}
func NewGrepAppClient() *GrepAppClient {
return &GrepAppClient{
BaseURL: "https://grep.app",
HTTPClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
type GrepSearchResult struct {
Repo string `json:"repo"`
Path string `json:"path"`
Filename string `json:"filename"`
Language string `json:"language,omitempty"`
Lines []struct {
Content string `json:"content"`
LineNumber int `json:"line_number"`
} `json:"lines,omitempty"`
URL string `json:"url"`
RepoURL string `json:"repo_url"`
BlobURL string `json:"blob_url"`
}
type GrepSearchResponse struct {
Hits struct {
Total int `json:"total"`
Hits []GrepSearchHit `json:"hits"`
Metadata map[string]int `json:"metadata,omitempty"`
} `json:"hits"`
}
type GrepSearchHit struct {
Source GrepSearchResult `json:"_source"`
}
type GrepSearchParams struct {
Query string
Language string
Repo string
Path string
Regex bool
CaseSensitive bool
WholeWords bool
}
func (c *GrepAppClient) Search(params GrepSearchParams) (*GrepSearchResponse, error) {
searchURL := fmt.Sprintf("%s/search", c.BaseURL)
values := url.Values{}
values.Set("q", params.Query)
values.Set("limit", "25")
if params.Language != "" {
values.Set("lang", params.Language)
}
if params.Repo != "" {
values.Set("repo", params.Repo)
}
if params.Path != "" {
values.Set("path", params.Path)
}
if params.Regex {
values.Set("regex", "true")
}
if params.CaseSensitive {
values.Set("case", "true")
}
if params.WholeWords {
values.Set("word", "true")
}
req, err := http.NewRequest("GET", searchURL+"?"+values.Encode(), nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("User-Agent", "Go-GrepApp-MCP/1.0")
req.Header.Set("Accept", "application/json")
req.Header.Set("Accept-Language", "en-US,en;q=0.5")
resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("error executing request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response: %w", err)
}
var searchResponse GrepSearchResponse
if err := json.Unmarshal(body, &searchResponse); err != nil {
return nil, fmt.Errorf("error parsing JSON: %w", err)
}
return &searchResponse, nil
}
func (c *GrepAppClient) GetFile(owner, repo, path, ref string) (string, error) {
apiURL := fmt.Sprintf("https://api.github.com/repos/%s/contents/%s", owner, path)
if ref != "" {
apiURL += "?ref=" + ref
}
req, err := http.NewRequest("GET", apiURL, nil)
if err != nil {
return "", fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("User-Agent", "Go-GrepApp-MCP/1.0")
req.Header.Set("Accept", "application/vnd.github.v3+json")
resp, err := c.HTTPClient.Do(req)
if err != nil {
return "", fmt.Errorf("error executing request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
}
var fileData map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&fileData); err != nil {
return "", fmt.Errorf("error parsing response: %w", err)
}
content, ok := fileData["content"].(string)
if !ok {
return "", fmt.Errorf("no content found in response")
}
decoded, err := decodeBase64(content)
if err != nil {
return "", fmt.Errorf("error decoding content: %w", err)
}
return decoded, nil
}
func decodeBase64(encoded string) (string, error) {
encoded = strings.ReplaceAll(encoded, "\n", "")
decoded, err := strconv.Unquote(`"` + encoded + `"`)
if err != nil {
return encoded, nil
}
return decoded, nil
}
type GitHubFileParams struct {
Owner string
Repo string
Path string
Ref string
}
func (c *GrepAppClient) GetFilesBatch(files []GitHubFileParams) ([]map[string]string, error) {
results := make([]map[string]string, 0, len(files))
for _, file := range files {
content, err := c.GetFile(file.Owner, file.Repo, file.Path, file.Ref)
if err != nil {
results = append(results, map[string]string{
"owner": file.Owner,
"repo": file.Repo,
"path": file.Path,
"error": err.Error(),
})
continue
}
results = append(results, map[string]string{
"owner": file.Owner,
"repo": file.Repo ,
"path": file.Path,
"content": content,
})
}
return results, nil
}