85 lines
2.9 KiB
Go
85 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
)
|
|
|
|
func registerTools(server *mcp.Server, api *searxngAPI) {
|
|
mcp.AddTool(server, &mcp.Tool{
|
|
Name: "search_web",
|
|
Description: "Search the operator's OAuth-protected SearXNG instance and return JSON search results.",
|
|
}, searchWebHandler(api))
|
|
}
|
|
|
|
type searchWebInput struct {
|
|
Query string `json:"query" jsonschema:"search query"`
|
|
Categories string `json:"categories,omitempty" jsonschema:"optional comma-separated SearXNG categories, e.g. general, images, news"`
|
|
Engines string `json:"engines,omitempty" jsonschema:"optional comma-separated SearXNG engines"`
|
|
Language string `json:"language,omitempty" jsonschema:"optional SearXNG language code"`
|
|
TimeRange string `json:"time_range,omitempty" jsonschema:"optional time range: day, month, year"`
|
|
SafeSearch *int `json:"safesearch,omitempty" jsonschema:"optional SearXNG safesearch value: 0, 1, or 2"`
|
|
Page int `json:"page,omitempty" jsonschema:"optional result page number, default 1"`
|
|
Limit int `json:"limit,omitempty" jsonschema:"max results to return (1-50, default 10)"`
|
|
}
|
|
|
|
type searchWebOutput struct {
|
|
Query string `json:"query"`
|
|
NumberOfResults int `json:"number_of_results,omitempty"`
|
|
Results []searxngResult `json:"results"`
|
|
Suggestions []string `json:"suggestions,omitempty"`
|
|
Answers []string `json:"answers,omitempty"`
|
|
Corrections []string `json:"corrections,omitempty"`
|
|
}
|
|
|
|
func searchWebHandler(api *searxngAPI) mcp.ToolHandlerFor[searchWebInput, searchWebOutput] {
|
|
return func(ctx context.Context, _ *mcp.CallToolRequest, in searchWebInput) (*mcp.CallToolResult, searchWebOutput, error) {
|
|
query := strings.TrimSpace(in.Query)
|
|
if query == "" {
|
|
return nil, searchWebOutput{}, errors.New("search_web requires query")
|
|
}
|
|
limit := clampInt(in.Limit, 1, 50, 10)
|
|
page := clampInt(in.Page, 1, 100, 1)
|
|
if in.SafeSearch != nil && (*in.SafeSearch < 0 || *in.SafeSearch > 2) {
|
|
return nil, searchWebOutput{}, errors.New("safesearch must be 0, 1, or 2")
|
|
}
|
|
resp, err := api.search(ctx, searchParams{
|
|
Query: query,
|
|
Categories: strings.TrimSpace(in.Categories),
|
|
Engines: strings.TrimSpace(in.Engines),
|
|
Language: strings.TrimSpace(in.Language),
|
|
TimeRange: strings.TrimSpace(in.TimeRange),
|
|
SafeSearch: in.SafeSearch,
|
|
Page: page,
|
|
Limit: limit,
|
|
})
|
|
if err != nil {
|
|
return nil, searchWebOutput{}, err
|
|
}
|
|
return nil, searchWebOutput{
|
|
Query: resp.Query,
|
|
NumberOfResults: resp.NumberOfResults,
|
|
Results: resp.Results,
|
|
Suggestions: resp.Suggestions,
|
|
Answers: resp.Answers,
|
|
Corrections: resp.Corrections,
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
func clampInt(v, min, max, dflt int) int {
|
|
if v <= 0 {
|
|
return dflt
|
|
}
|
|
if v < min {
|
|
return min
|
|
}
|
|
if v > max {
|
|
return max
|
|
}
|
|
return v
|
|
}
|