MCPcopy
hub / github.com/docker/docker-agent / CallTool

Method CallTool

pkg/tools/builtin/fetch/fetch.go:79–169  ·  view source on GitHub ↗
(ctx context.Context, params ToolArgs)

Source from the content-addressed store, hash-verified

77}
78
79func (h *fetchHandler) CallTool(ctx context.Context, params ToolArgs) (*tools.ToolCallResult, error) {
80 if len(params.URLs) == 0 {
81 return nil, errors.New("at least one URL is required")
82 }
83
84 // Decorate the active runtime.tool.handler span with the requested
85 // URLs. Strip query params and userinfo first: query strings often
86 // carry signed-URL tokens, OAuth codes, or session IDs, and userinfo
87 // carries credentials inline. The path stays intact so dashboards
88 // can still answer "which sites/endpoints did the agent hit?" — the
89 // HTTP CLIENT child span emitted by `httpclient.WrapWithOTel` below
90 // retains the full URL under `http.url` for callers that opt into
91 // that backend's full-URL capture.
92 if span := trace.SpanFromContext(ctx); span.IsRecording() {
93 attrs := []attribute.KeyValue{
94 attribute.Int("cagent.tool.fetch.url_count", len(params.URLs)),
95 attribute.StringSlice("cagent.tool.fetch.urls", sanitizeFetchURLs(params.URLs)),
96 }
97 if params.Format != "" {
98 attrs = append(attrs, attribute.String("cagent.tool.fetch.format", params.Format))
99 }
100 span.SetAttributes(attrs...)
101 }
102
103 // Transport: by default we install [httpclient.SSRFDialControl] on the
104 // dialer so the fetch tool refuses connections to loopback, RFC1918,
105 // link-local (incl. cloud metadata at 169.254.169.254), multicast and
106 // the unspecified address — even when DNS for an otherwise-public host
107 // resolves there. Operators who legitimately need to call internal
108 // services opt in via `allow_private_ips: true`.
109 var transport http.RoundTripper = httpclient.NewSSRFSafeTransport()
110 if h.allowPrivateIPs {
111 transport = http.DefaultTransport
112 }
113
114 headers := h.expander.ExpandMap(ctx, h.headers)
115
116 client := &http.Client{
117 Timeout: h.timeout,
118 Transport: httpclient.WrapWithOTel(transport),
119 // Re-check the domain allow/deny lists on every redirect: without this,
120 // an allowed origin could redirect into a denied one and bypass the
121 // policy. The 10-redirect cap mirrors the net/http default.
122 CheckRedirect: func(req *http.Request, via []*http.Request) error {
123 if len(via) >= 10 {
124 return errors.New("stopped after 10 redirects")
125 }
126 // Strip caller-supplied headers when redirecting to a different
127 // host so credentials (Authorization, X-Api-Key, ...) never leak
128 // to a third-party host. Go's stdlib already strips a small
129 // allow-list (Authorization, WWW-Authenticate, Cookie) on cross-
130 // domain redirects but does NOT strip arbitrary custom headers,
131 // so we strip everything the operator configured. via[0] is the
132 // original request; comparing req.URL.Host against it (rather
133 // than the previous hop) guarantees that headers cannot reappear
134 // after a chain like A -> B -> A.
135 if origHost := via[0].URL.Host; origHost != req.URL.Host {
136 for k := range headers {

Callers

nothing calls this directly

Calls 14

checkDomainAllowedMethod · 0.95
fetchURLMethod · 0.95
NewSSRFSafeTransportFunction · 0.92
WrapWithOTelFunction · 0.92
ResultErrorFunction · 0.92
ResultSuccessFunction · 0.92
ResultJSONFunction · 0.92
sanitizeFetchURLsFunction · 0.85
ExpandMapMethod · 0.80
DurationMethod · 0.80
IsRecordingMethod · 0.65
NewMethod · 0.45

Tested by

no test coverage detected