MCPcopy Create free account
hub / github.com/QodeXcli/QodeX / execute

Method execute

src/tools/web/web-fetch.ts:175–272  ·  view source on GitHub ↗
(args: z.infer<typeof WebFetchArgs>, ctx: ToolContext)

Source from the content-addressed store, hash-verified

173 argsSchema = WebFetchArgs;
174
175 async execute(args: z.infer<typeof WebFetchArgs>, ctx: ToolContext): Promise<ToolResult> {
176 if (isPrivateOrLocal(args.url)) {
177 return {
178 content: `[WEB_FETCH_BLOCKED] ${args.url} is a private/local address. Use browser_navigate if this is your own dev server, or dev_server_log to read its output.`,
179 isError: true,
180 };
181 }
182
183 const controller = new AbortController();
184 const timer = setTimeout(() => controller.abort(), args.timeout_ms ?? 30_000);
185
186 try {
187 const response = await proxyFetch(args.url, {
188 signal: ctx.signal ? mergeAbortSignals(ctx.signal, controller.signal) : controller.signal,
189 redirect: 'follow',
190 headers: {
191 'User-Agent': args.user_agent ?? DEFAULT_UA,
192 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
193 'Accept-Language': 'en-US,en;q=0.9',
194 },
195 });
196
197 if (!response.ok) {
198 return {
199 content: `[WEB_FETCH_ERROR] HTTP ${response.status} ${response.statusText} for ${args.url}`,
200 isError: true,
201 };
202 }
203
204 const contentType = response.headers.get('content-type') ?? '';
205
206 // Guard against binary payloads. Reading an image/PDF/font/archive as text
207 // dumps garbage bytes into the model's context (and burns tokens). Detect
208 // by content-type and bail with a useful message instead of the raw bytes.
209 const isBinary = /^(image|audio|video|font)\//i.test(contentType)
210 || /(application\/(octet-stream|pdf|zip|gzip|x-tar|x-7z|wasm|x-protobuf)|application\/[^;]*\+(zip|octet))/i.test(contentType);
211 if (isBinary) {
212 const lenHeader = response.headers.get('content-length');
213 return {
214 content: `URL: ${response.url}\nStatus: ${response.status}\nContent-Type: ${contentType}\n\n[binary content — not fetched as text]\n` +
215 `This is a binary file (${contentType}${lenHeader ? `, ${lenHeader} bytes` : ''}). web_fetch returns text only; ` +
216 `it won't read images, PDFs, fonts, or archives. If you need to analyze an image, note its URL and describe what you need — don't re-fetch it.`,
217 metadata: { url: response.url, status: response.status, contentType, binary: true },
218 };
219 }
220
221 const body = await response.text();
222 const format = args.format ?? 'text';
223 const maxChars = args.max_chars ?? 25_000;
224
225 let output: string;
226 if (format === 'html' || !contentType.includes('html')) {
227 output = body;
228 } else if (format === 'markdown') {
229 output = stripBase64Images(htmlToMarkdown(body)); // drop base64 token-bombs; keep http image links
230 } else {
231 output = htmlToText(body);
232 }

Callers

nothing calls this directly

Calls 10

proxyFetchFunction · 0.85
mergeAbortSignalsFunction · 0.85
stripBase64ImagesFunction · 0.85
htmlToMarkdownFunction · 0.85
htmlToTextFunction · 0.85
storeFullTextFunction · 0.85
selectRelevantPassagesFunction · 0.85
abortMethod · 0.80
isPrivateOrLocalFunction · 0.70
getMethod · 0.45

Tested by

no test coverage detected