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

Method execute

src/tools/web/http-request.ts:48–160  ·  view source on GitHub ↗
(args: z.infer<typeof HttpRequestArgs>, ctx: ToolContext)

Source from the content-addressed store, hash-verified

46
47export class HttpRequestTool extends Tool<z.infer<typeof HttpRequestArgs>> {
48 name = 'http_request';
49 description = 'Make an HTTP request to an external or local URL. For local dev APIs pass allow_local=true. Returns status, headers, and a head+tail excerpt (~6KB) of the response body with the total size noted — pass fullBody=true if you genuinely need the whole body (up to 1MB). Destructive when method is POST/PUT/PATCH/DELETE — those mutate remote state.';
50 isReadOnly = false; // POST/PUT/DELETE mutate
51 isDestructive = true;
52 argsSchema = HttpRequestArgs;
53
54 async execute(args: z.infer<typeof HttpRequestArgs>, ctx: ToolContext): Promise<ToolResult> {
55 const method = args.method ?? 'GET';
56 const timeoutMs = (args.timeout_seconds ?? 30) * 1000;
57 const maxBytes = args.max_response_bytes ?? 1_048_576;
58
59 // Build final URL with query string
60 let urlStr = args.url;
61 if (args.query) {
62 const u = new URL(urlStr);
63 for (const [k, v] of Object.entries(args.query)) u.searchParams.set(k, v);
64 urlStr = u.toString();
65 }
66
67 let parsed: URL;
68 try { parsed = new URL(urlStr); } catch {
69 return { content: `[HTTP_REQUEST_ERROR] Invalid URL: ${urlStr}`, isError: true };
70 }
71
72 if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
73 return { content: `[HTTP_REQUEST_ERROR] Only http:/https: allowed. Got: ${parsed.protocol}`, isError: true };
74 }
75
76 if (PRIVATE_HOST_RE.test(parsed.hostname) && !args.allow_local) {
77 return {
78 content: `[HTTP_REQUEST_BLOCKED] Refusing to call private host ${parsed.hostname}. Pass allow_local=true if this is intentional (dev API testing).`,
79 isError: true,
80 };
81 }
82
83 const startTime = Date.now();
84 const abort = new AbortController();
85 const timer = setTimeout(() => abort.abort('timeout'), timeoutMs);
86 // Cascade outer signal
87 if (ctx.signal) {
88 if (ctx.signal.aborted) abort.abort('cancelled');
89 else ctx.signal.addEventListener('abort', () => abort.abort('cancelled'), { once: true });
90 }
91
92 try {
93 const resp = await proxyFetch(urlStr, {
94 method,
95 headers: args.headers,
96 body: args.body,
97 signal: abort.signal,
98 redirect: args.follow_redirects === false ? 'manual' : 'follow',
99 });
100
101 // Pick a subset of headers to surface
102 const headersOut: Record<string, string> = {};
103 const interesting = new Set(['content-type', 'content-length', 'content-encoding', 'cache-control', 'set-cookie', 'location', 'server', 'date', 'etag', 'last-modified', 'access-control-allow-origin', 'x-ratelimit-remaining', 'x-ratelimit-limit', 'x-request-id']);
104 resp.headers.forEach((v, k) => { if (interesting.has(k.toLowerCase())) headersOut[k] = v; });
105

Callers

nothing calls this directly

Calls 6

proxyFetchFunction · 0.85
abortMethod · 0.80
setMethod · 0.45
hasMethod · 0.45
pushMethod · 0.45
getMethod · 0.45

Tested by

no test coverage detected