Textwell MCP Server
by worldnine
- tools
package tools
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
htmltomarkdownnnn "github.com/JohannesKaufmann/html-to-markdown/v2"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"github.com/nguyenvanduocit/all-in-one-model-context-protocol/services"
"github.com/nguyenvanduocit/all-in-one-model-context-protocol/util"
"github.com/tidwall/gjson"
)
func RegisterWebSearchTool(s *server.MCPServer) {
tool := mcp.NewTool("web_search",
mcp.WithDescription("Search the web using Brave Search API"),
mcp.WithString("query", mcp.Required(), mcp.Description("Query to search for (max 400 chars, 50 words)")),
mcp.WithNumber("count", mcp.DefaultNumber(5), mcp.Description("Number of results (1-20, default 5)")),
mcp.WithString("country", mcp.DefaultString("ALL"), mcp.Description("Country code")),
)
s.AddTool(tool, util.ErrorGuard(webSearchHandler))
}
type SearchResult struct {
Title string `json:"title"`
URL string `json:"url"`
Description string `json:"description"`
Type string `json:"type"`
Age string `json:"age"`
}
func webSearchHandler(arguments map[string]interface{}) (*mcp.CallToolResult, error) {
query, ok := arguments["query"].(string)
if !ok {
return mcp.NewToolResultError("query must be a string"), nil
}
count := 10
if countArg, ok := arguments["count"].(float64); ok {
count = int(countArg)
if count < 1 {
count = 1
} else if count > 20 {
count = 20
}
}
country := "ALL"
if countryArg, ok := arguments["country"].(string); ok {
country = countryArg
}
apiKey := os.Getenv("BRAVE_API_KEY")
if apiKey == "" {
return mcp.NewToolResultError("BRAVE_API_KEY environment variable is required"), nil
}
baseURL := "https://api.search.brave.com/res/v1/web/search"
params := url.Values{}
params.Add("q", query)
params.Add("count", fmt.Sprintf("%d", count))
params.Add("country", country)
req, err := http.NewRequest("GET", baseURL+"?"+params.Encode(), nil)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("Failed to create request: %v", err)), nil
}
req.Header.Set("Accept", "application/json")
req.Header.Set("X-Subscription-Token", apiKey)
resp, err := services.DefaultHttpClient().Do(req)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("Failed to perform search: %v", err)), nil
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("Failed to read response: %v", err)), nil
}
if resp.StatusCode != http.StatusOK {
return mcp.NewToolResultError(fmt.Sprintf("API request failed: %s", string(body))), nil
}
var results []*SearchResult
gbody := gjson.ParseBytes(body)
videoResults := gbody.Get("videos.results")
for _, video := range videoResults.Array() {
mdContent, err := htmltomarkdownnnn.ConvertString(video.Get("description").String())
if err != nil {
return nil, fmt.Errorf("failed to convert HTML to Markdown: %v", err)
}
results = append(results, &SearchResult{
Title: video.Get("title").String(),
URL: video.Get("url").String(),
Description: mdContent,
Type: "video",
Age: video.Get("age").String(),
})
}
webResults := gbody.Get("web.results")
for _, web := range webResults.Array() {
mdContent, err := htmltomarkdownnnn.ConvertString(web.Get("description").String())
if err != nil {
return nil, fmt.Errorf("failed to convert HTML to Markdown: %v", err)
}
results = append(results, &SearchResult{
Title: web.Get("title").String(),
URL: web.Get("url").String(),
Description: mdContent,
Type: "web",
Age: web.Get("age").String(),
})
}
if len(results) == 0 {
return mcp.NewToolResultError("No results found, pls try again with a different query"), nil
}
responseText := ""
for _, result := range results {
responseText += fmt.Sprintf("Title: %s\nURL: %s\nDescription: %s\nType: %s\nAge: %s\n\n",
result.Title, result.URL, result.Description, result.Type, result.Age)
}
return mcp.NewToolResultText(responseText), nil
}