Skip to content

Commit 8e2e6a4

Browse files
authored
Merge pull request #396 from rumpl/feat-fetch
Rework the fetch tool
2 parents 19d61fd + 35ee317 commit 8e2e6a4

4 files changed

Lines changed: 192 additions & 261 deletions

File tree

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.25.1
44

55
require (
66
dario.cat/mergo v1.0.2
7+
github.com/JohannesKaufmann/html-to-markdown/v2 v2.4.0
78
github.com/Microsoft/go-winio v0.6.2
89
github.com/alecthomas/chroma/v2 v2.20.0
910
github.com/anthropics/anthropic-sdk-go v1.13.0
@@ -18,6 +19,7 @@ require (
1819
github.com/goccy/go-yaml v1.18.0
1920
github.com/google/go-containerregistry v0.20.6
2021
github.com/google/uuid v1.6.0
22+
github.com/k3a/html2text v1.2.1
2123
github.com/labstack/echo/v4 v4.13.4
2224
github.com/mark3labs/mcp-go v0.41.1
2325
github.com/mattn/go-runewidth v0.0.16
@@ -38,6 +40,7 @@ require (
3840
cloud.google.com/go v0.121.6 // indirect
3941
cloud.google.com/go/auth v0.16.5 // indirect
4042
cloud.google.com/go/compute/metadata v0.8.0 // indirect
43+
github.com/JohannesKaufmann/dom v0.2.0 // indirect
4144
github.com/aymerick/douceur v0.2.0 // indirect
4245
github.com/bahlo/generic-list-go v0.2.0 // indirect
4346
github.com/buger/jsonparser v1.1.1 // indirect

go.sum

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcao
66
cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
77
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
88
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
9+
github.com/JohannesKaufmann/dom v0.2.0 h1:1bragmEb19K8lHAqgFgqCpiPCFEZMTXzOIEjuxkUfLQ=
10+
github.com/JohannesKaufmann/dom v0.2.0/go.mod h1:57iSUl5RKric4bUkgos4zu6Xt5LMHUnw3TF1l5CbGZo=
11+
github.com/JohannesKaufmann/html-to-markdown/v2 v2.4.0 h1:C0/TerKdQX9Y9pbYi1EsLr5LDNANsqunyI/btpyfCg8=
12+
github.com/JohannesKaufmann/html-to-markdown/v2 v2.4.0/go.mod h1:OLaKh+giepO8j7teevrNwiy/fwf8LXgoc9g7rwaE1jk=
913
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
1014
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
1115
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
@@ -107,6 +111,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU
107111
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
108112
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
109113
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
114+
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
115+
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
110116
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
111117
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
112118
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
@@ -119,6 +125,10 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
119125
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
120126
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
121127
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
128+
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
129+
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
130+
github.com/k3a/html2text v1.2.1 h1:nvnKgBvBR/myqrwfLuiqecUtaK1lB9hGziIJKatNFVY=
131+
github.com/k3a/html2text v1.2.1/go.mod h1:ieEXykM67iT8lTvEWBh6fhpH4B23kB9OMKPdIBmgUqA=
122132
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
123133
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
124134
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -167,8 +177,16 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN
167177
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
168178
github.com/sashabaranov/go-openai v1.41.2 h1:vfPRBZNMpnqu8ELsclWcAvF19lDNgh1t6TVfFFOPiSM=
169179
github.com/sashabaranov/go-openai v1.41.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
180+
github.com/sebdah/goldie/v2 v2.7.1 h1:PkBHymaYdtvEkZV7TmyqKxdmn5/Vcj+8TpATWZjnG5E=
181+
github.com/sebdah/goldie/v2 v2.7.1/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
182+
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
183+
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
170184
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
171185
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
186+
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
187+
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
188+
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
189+
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
172190
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
173191
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
174192
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
@@ -236,26 +254,31 @@ go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOV
236254
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
237255
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
238256
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
257+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
239258
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
240259
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
241260
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
242261
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
243262
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
244263
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
264+
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
245265
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
246266
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
247267
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
248268
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
269+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
249270
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
250271
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
251272
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
252273
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
253274
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
254275
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
276+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
255277
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
256278
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
257279
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
258280
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
281+
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
259282
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
260283
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
261284
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=

pkg/tools/builtin/fetch.go

Lines changed: 78 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,30 @@ import (
88
"net/http"
99
"net/url"
1010
"reflect"
11+
"strings"
1112
"time"
1213

14+
htmltomarkdown "github.com/JohannesKaufmann/html-to-markdown/v2"
15+
"github.com/k3a/html2text"
16+
1317
"github.com/docker/cagent/pkg/tools"
1418
)
1519

1620
type FetchTool struct {
17-
timeout time.Duration
18-
client *http.Client
21+
handler *fetchHandler
1922
}
2023

2124
var _ tools.ToolSet = (*FetchTool)(nil)
2225

2326
type fetchHandler struct {
24-
tool *FetchTool
27+
timeout time.Duration
2528
}
2629

2730
func (h *fetchHandler) CallTool(ctx context.Context, toolCall tools.ToolCall) (*tools.ToolCallResult, error) {
2831
var params struct {
29-
URLs []string `json:"urls"`
30-
Headers map[string]string `json:"headers,omitempty"`
31-
Method string `json:"method,omitempty"`
32-
Timeout int `json:"timeout,omitempty"`
33-
UserAgent string `json:"userAgent,omitempty"`
32+
URLs []string `json:"urls"`
33+
Timeout int `json:"timeout,omitempty"`
34+
Format string `json:"format,omitempty"`
3435
}
3536

3637
if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &params); err != nil {
@@ -41,24 +42,18 @@ func (h *fetchHandler) CallTool(ctx context.Context, toolCall tools.ToolCall) (*
4142
return nil, fmt.Errorf("at least one URL is required")
4243
}
4344

44-
// Set defaults
45-
if params.Method == "" {
46-
params.Method = "GET"
47-
}
48-
if params.UserAgent == "" {
49-
params.UserAgent = "cagent-fetch/1.0"
50-
}
51-
5245
// Set timeout if specified
53-
client := h.tool.client
46+
client := &http.Client{
47+
Timeout: h.timeout,
48+
}
5449
if params.Timeout > 0 {
5550
timeout := time.Duration(params.Timeout) * time.Second
5651
client = &http.Client{Timeout: timeout}
5752
}
5853

5954
var results []FetchResult
6055
for _, urlStr := range params.URLs {
61-
result := h.fetchURL(ctx, client, urlStr, params.Method, params.Headers, params.UserAgent)
56+
result := h.fetchURL(ctx, client, urlStr, params.Format)
6257
results = append(results, result)
6358
}
6459

@@ -93,7 +88,7 @@ type FetchResult struct {
9388
Error string `json:"error,omitempty"`
9489
}
9590

96-
func (h *fetchHandler) fetchURL(ctx context.Context, client *http.Client, urlStr, method string, headers map[string]string, userAgent string) FetchResult {
91+
func (h *fetchHandler) fetchURL(ctx context.Context, client *http.Client, urlStr, format string) FetchResult {
9792
result := FetchResult{URL: urlStr}
9893

9994
// Validate URL
@@ -116,18 +111,24 @@ func (h *fetchHandler) fetchURL(ctx context.Context, client *http.Client, urlStr
116111
}
117112

118113
// Create request
119-
req, err := http.NewRequestWithContext(ctx, method, urlStr, http.NoBody)
114+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, http.NoBody)
120115
if err != nil {
121116
result.Error = fmt.Sprintf("failed to create request: %v", err)
122117
return result
123118
}
124119

125120
// Set User-Agent
126-
req.Header.Set("User-Agent", userAgent)
127-
128-
// Set custom headers
129-
for key, value := range headers {
130-
req.Header.Set(key, value)
121+
req.Header.Set("User-Agent", "cagent/1.0")
122+
123+
switch format {
124+
case "markdown":
125+
req.Header.Set("Accept", "text/markdown;q=1.0, text/plain;q=0.9, text/html;q=0.7, */*;q=0.1")
126+
case "html":
127+
req.Header.Set("Accept", "text/html;q=1.0, text/plain;q=0.8, */*;q=0.1")
128+
case "text":
129+
req.Header.Set("Accept", "text/plain;q=1.0, text/markdown;q=0.9, text/html;q=0.8, */*;q=0.1")
130+
default:
131+
req.Header.Set("Accept", "text/plain;q=1.0, */*;q=0.1")
131132
}
132133

133134
// Execute request
@@ -143,66 +144,87 @@ func (h *fetchHandler) fetchURL(ctx context.Context, client *http.Client, urlStr
143144
result.ContentType = resp.Header.Get("Content-Type")
144145

145146
// Read response body
146-
body, err := io.ReadAll(resp.Body)
147+
maxSize := int64(1 << 20) // 1MB
148+
body, err := io.ReadAll(io.LimitReader(resp.Body, maxSize))
147149
if err != nil {
148150
result.Error = fmt.Sprintf("failed to read response body: %v", err)
149151
return result
150152
}
151153

152-
result.ContentLength = len(body)
153-
result.Body = string(body)
154+
contentType := resp.Header.Get("Content-Type")
155+
156+
switch format {
157+
case "markdown":
158+
if strings.Contains(contentType, "text/html") {
159+
result.Body = htmlToMarkdown(string(body))
160+
} else {
161+
result.Body = string(body)
162+
}
163+
case "html":
164+
result.Body = string(body)
165+
case "text":
166+
if strings.Contains(contentType, "text/html") {
167+
result.Body = htmlToText(string(body))
168+
} else {
169+
result.Body = string(body)
170+
}
171+
default:
172+
result.Body = string(body)
173+
}
174+
175+
result.ContentLength = len(result.Body)
154176

155177
return result
156178
}
157179

180+
func htmlToMarkdown(html string) string {
181+
markdown, err := htmltomarkdown.ConvertString(html)
182+
if err != nil {
183+
return html
184+
}
185+
return markdown
186+
}
187+
188+
func htmlToText(html string) string {
189+
return html2text.HTML2Text(html)
190+
}
191+
158192
func NewFetchTool(options ...FetchToolOption) *FetchTool {
159193
tool := &FetchTool{
160-
timeout: 30 * time.Second,
194+
handler: &fetchHandler{
195+
timeout: 30 * time.Second,
196+
},
161197
}
162198

163-
// Apply options
164199
for _, opt := range options {
165200
opt(tool)
166201
}
167202

168-
// Create HTTP client with timeout
169-
tool.client = &http.Client{
170-
Timeout: tool.timeout,
171-
}
172-
173203
return tool
174204
}
175205

176206
type FetchToolOption func(*FetchTool)
177207

178208
func WithTimeout(timeout time.Duration) FetchToolOption {
179209
return func(t *FetchTool) {
180-
t.timeout = timeout
210+
t.handler.timeout = timeout
181211
}
182212
}
183213

184214
func (t *FetchTool) Instructions() string {
185-
return `## Fetch Tool Instructions
215+
return `## "fetch" tool instructions
186216
187217
This tool allows you to fetch content from HTTP and HTTPS URLs.
188218
189-
### Features
190-
- Support for multiple URLs in a single call
191-
- Customizable HTTP headers
192-
- Configurable request method (GET, POST, etc.)
193-
- Timeout control
194-
- User-Agent customization
219+
FEATURES
195220
196-
### Security
197-
- Only HTTP and HTTPS protocols are supported
198-
- No local file access or other protocols
199-
- Request timeouts prevent hanging requests
221+
- Support for multiple URLs in a single call
222+
- Returns response body and metadata (status code, content type, length)
223+
- Specify the output format (text, markdown, html)
200224
201-
### Usage Tips
225+
USAGE TIPS
202226
- Use single URLs for simple content fetching
203-
- Use multiple URLs for batch operations
204-
- Set appropriate headers for APIs that require authentication
205-
- Consider timeout values for slow or large responses`
227+
- Use multiple URLs for batch operations`
206228
}
207229

208230
func (t *FetchTool) Tools(context.Context) ([]tools.Tool, error) {
@@ -226,35 +248,23 @@ func (t *FetchTool) Tools(context.Context) ([]tools.Tool, error) {
226248
"description": "Array of URLs to fetch",
227249
"minItems": 1,
228250
},
229-
"method": map[string]any{
251+
"format": map[string]any{
230252
"type": "string",
231-
"description": "HTTP method to use (default: GET)",
232-
"default": "GET",
233-
"enum": []string{"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH"},
234-
},
235-
"headers": map[string]any{
236-
"type": "object",
237-
"additionalProperties": map[string]any{
238-
"type": "string",
239-
},
240-
"description": "Optional HTTP headers to send with the request",
253+
"description": "The format to return the content in (text, markdown, or html)",
254+
"enum": []string{"text", "markdown", "html"},
241255
},
242256
"timeout": map[string]any{
243257
"type": "integer",
244258
"description": "Request timeout in seconds (default: 30)",
245259
"minimum": 1,
246260
"maximum": 300,
247261
},
248-
"userAgent": map[string]any{
249-
"type": "string",
250-
"description": "Custom User-Agent header (default: cagent-fetch/1.0)",
251-
},
252262
},
253-
Required: []string{"urls"},
263+
Required: []string{"urls", "format"},
254264
},
255265
OutputSchema: tools.ToOutputSchemaSchema(reflect.TypeFor[string]()),
256266
},
257-
Handler: (&fetchHandler{tool: t}).CallTool,
267+
Handler: t.handler.CallTool,
258268
},
259269
}, nil
260270
}

0 commit comments

Comments
 (0)