HTTP GET Request
http-getMake HTTP GET requests to retrieve data from any URL, with built-in secret substitution for secure API calls.
Instructions
Make an HTTP GET request to a specified URL. Supports secret substitution using {secrets.key} syntax where 'key' corresponds to HAL_SECRET_KEY environment variables.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | ||
| headers | No |
Implementation Reference
- src/index.ts:571-584 (registration)Registration of the 'http-get' tool on the MCP server, defining its name, description, input schema (url as required string, headers as optional record), and handler callback that delegates to makeHttpRequest.
server.registerTool( "http-get", { title: "HTTP GET Request", description: "Make an HTTP GET request to a specified URL. Supports secret substitution using {secrets.key} syntax where 'key' corresponds to HAL_SECRET_KEY environment variables.", inputSchema: { url: z.string().url(), headers: z.record(z.string()).optional() } }, async ({ url, headers = {} }: { url: string; headers?: Record<string, string> }) => { return makeHttpRequest('GET', url, { headers }); } ); - src/index.ts:576-579 (schema)Input schema for http-get: requires a valid URL string, accepts optional headers as a record of strings.
inputSchema: { url: z.string().url(), headers: z.record(z.string()).optional() } - src/index.ts:343-447 (handler)Core HTTP request handler used by http-get (and all other http-* tools). Handles secret substitution, URL validation, global whitelist/blacklist checks, fetch execution, response parsing (JSON/text), and secret redaction from responses.
async function makeHttpRequest( method: string, url: string, options: { headers?: Record<string, string>; body?: string; queryParams?: Record<string, any>; } = {} ) { try { const { headers = {}, body, queryParams = {} } = options; // First, substitute secrets in URL to get the final URL for validation // We need to do this in two passes to handle URL restrictions properly const processedUrl = substituteSecrets(url, url); // Now substitute secrets in headers, body, and query parameters using the processed URL const processedHeaders = substituteSecretsInObject(headers, processedUrl); const processedBody = body ? substituteSecrets(body, processedUrl) : body; const processedQueryParams = substituteSecretsInObject(queryParams, processedUrl); // Build URL with query parameters const urlObj = new URL(processedUrl); Object.entries(processedQueryParams).forEach(([key, value]) => { if (value !== undefined && value !== null) { urlObj.searchParams.set(key, String(value)); } }); const finalUrl = urlObj.toString(); // Check global URL whitelist/blacklist const urlCheck = isUrlAllowedGlobal(finalUrl); if (!urlCheck.allowed) { throw new Error(urlCheck.reason || 'URL is not allowed'); } const defaultHeaders = { 'User-Agent': 'HAL-MCP/1.0.0', ...processedHeaders }; // Add Content-Type for methods that typically send data if (['POST', 'PUT', 'PATCH'].includes(method.toUpperCase()) && processedBody && !('Content-Type' in processedHeaders)) { (defaultHeaders as any)['Content-Type'] = 'application/json'; } const response = await fetch(finalUrl, { method: method.toUpperCase(), headers: defaultHeaders, body: processedBody }); const contentType = response.headers.get('content-type') || 'text/plain'; let content: string; // HEAD requests don't have a body by design if (method.toUpperCase() === 'HEAD') { content = '(No body - HEAD request)'; } else { try { if (contentType.includes('application/json')) { const text = await response.text(); if (text.trim()) { content = JSON.stringify(JSON.parse(text), null, 2); } else { content = '(Empty response)'; } } else { content = await response.text(); } } catch (parseError) { // If JSON parsing fails, try to get text try { content = await response.text(); } catch (textError) { content = '(Unable to parse response)'; } } } // Redact secrets from response headers and content before returning const redactedHeaders = Array.from(response.headers.entries()) .map(([key, value]) => `${key}: ${redactSecretsFromText(value)}`) .join('\n'); const redactedContent = redactSecretsFromText(content); return { content: [{ type: "text" as const, text: `Status: ${response.status} ${response.statusText}\n\nHeaders:\n${redactedHeaders}\n\nBody:\n${redactedContent}` }] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; const redactedErrorMessage = redactSecretsFromText(errorMessage); return { content: [{ type: "text" as const, text: `Error making ${method.toUpperCase()} request: ${redactedErrorMessage}` }], isError: true }; } } - src/index.ts:105-114 (helper)Helper that matches URLs against wildcard patterns, used by URL validation checks that protect http-get requests.
function matchesUrlPattern(url: string, pattern: string): boolean { // Convert pattern to regex // Escape special regex characters except * const escapedPattern = pattern .replace(/[.+?^${}()|[\]\\]/g, '\\$&') .replace(/\*/g, '.*'); const regex = new RegExp(`^${escapedPattern}$`, 'i'); return regex.test(url); } - src/index.ts:133-169 (helper)Helper that enforces global URL whitelist/blacklist filters (HAL_WHITELIST_URLS, HAL_BLACKLIST_URLS) for http-get requests.
function isUrlAllowedGlobal(url: string): { allowed: boolean; reason?: string } { const { whitelist, blacklist } = loadGlobalUrlFilters(); // If both whitelist and blacklist are provided, prioritize whitelist if (whitelist && blacklist) { console.error('Warning: Both HAL_WHITELIST_URLS and HAL_BLACKLIST_URLS are set. Whitelist takes precedence.'); } // Check whitelist first (if present, only whitelisted URLs are allowed) if (whitelist) { for (const pattern of whitelist) { if (matchesUrlPattern(url, pattern)) { return { allowed: true }; } } return { allowed: false, reason: `URL '${url}' is not in the whitelist. Allowed patterns: ${whitelist.join(', ')}` }; } // Check blacklist (if present, blacklisted URLs are denied) if (blacklist) { for (const pattern of blacklist) { if (matchesUrlPattern(url, pattern)) { return { allowed: false, reason: `URL '${url}' is blacklisted. Blocked patterns: ${blacklist.join(', ')}` }; } } return { allowed: true }; } // If neither whitelist nor blacklist is set, allow all URLs return { allowed: true }; }