Skip to main content
Glama
DeanWard

HAL (HTTP API Layer)

http-post

Send HTTP POST requests to specified URLs with optional body, headers, and secret substitution using {secrets.key} syntax, simplifying API interactions within the HAL server environment.

Instructions

Make an HTTP POST request to a specified URL with optional body and headers. Supports secret substitution using {secrets.key} syntax in URL, headers, and body where 'key' corresponds to HAL_SECRET_KEY environment variables.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
bodyNo
contentTypeNoapplication/json
headersNo
urlYes

Implementation Reference

  • src/index.ts:586-605 (registration)
    Registration of the 'http-post' tool using server.registerTool, including title, description, inputSchema, and the handler function.
    server.registerTool(
      "http-post",
      {
        title: "HTTP POST Request",
        description: "Make an HTTP POST request to a specified URL with optional body and headers. Supports secret substitution using {secrets.key} syntax in URL, headers, and body where 'key' corresponds to HAL_SECRET_KEY environment variables.",
        inputSchema: {
          url: z.string().url(),
          body: z.string().optional(),
          headers: z.record(z.string()).optional(),
          contentType: z.string().default('application/json')
        }
      },
      async ({ url, body, headers = {}, contentType }: { url: string; body?: string; headers?: Record<string, string>; contentType: string }) => {
        const requestHeaders = {
          'Content-Type': contentType,
          ...headers
        };
        return makeHttpRequest('POST', url, { headers: requestHeaders, body });
      }
    );
  • Zod input schema for the http-post tool, validating url (required URL), body (optional string), headers (optional record), contentType (default 'application/json').
    inputSchema: {
      url: z.string().url(),
      body: z.string().optional(),
      headers: z.record(z.string()).optional(),
      contentType: z.string().default('application/json')
    }
  • Specific handler for http-post tool: constructs request headers with Content-Type and calls the core makeHttpRequest with method 'POST'.
    async ({ url, body, headers = {}, contentType }: { url: string; body?: string; headers?: Record<string, string>; contentType: string }) => {
      const requestHeaders = {
        'Content-Type': contentType,
        ...headers
      };
      return makeHttpRequest('POST', url, { headers: requestHeaders, body });
    }
  • Core helper function implementing HTTP requests for all http-* tools: handles secret substitution in URL/headers/body/query, URL allow/block lists, performs fetch, processes response (JSON/text), redacts secrets from output.
    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
         };
      }
    }
Install Server

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DeanWard/HAL'

If you have feedback or need assistance with the MCP directory API, please join our Discord server