Skip to main content
Glama

get_request_details

Read-only

Retrieve detailed request and response headers, body preview, timing, and size for a specific network request. Save full body to disk when needed for API debugging.

Instructions

[may return preview+token] Get detailed information about a specific network request by index (from list_network_requests). Returns request/response headers, body (truncated at 500 chars), timing, and size. Request bodies with passwords are automatically masked. If a request or response body exceeds 500 chars, includes a preview and a one-time confirm_output token that, when called, saves the full body to disk under ./.mcp-web-inspector/network-bodies/ and returns the file path(s). Essential for debugging API responses and investigating failed requests.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
indexYesIndex of the request from list_network_requests output (e.g., [0], [1], etc.)

Implementation Reference

  • The execute() method of GetRequestDetailsTool class. Retrieves network request details by index: shows method, URL, status, timing, size, important headers (redacting auth/cookie/set-cookie unless exposeSensitiveNetworkData is set), request body (masking passwords), response body (truncated at 500 chars). Provides confirm_output token to save full bodies to disk when truncated.
      async execute(args: GetRequestDetailsArgs, context: ToolContext): Promise<ToolResponse> {
        return this.safeExecute(context, async () => {
          const { index } = args;
    
          const { getNetworkLog, getSessionConfig } = await import('../../../toolHandler.js');
          const networkLog = getNetworkLog();
          const sessionConfig = getSessionConfig();
          const exposeSensitive = Boolean(sessionConfig?.exposeSensitiveNetworkData);
    
          if (index < 0 || index >= networkLog.length) {
            return {
              content: [{
                type: "text",
                text: `Error: Invalid index ${index}. Valid range: 0-${networkLog.length - 1}`
              }],
              isError: true
            };
          }
    
          const req = networkLog[index];
    
          // Helper to look up headers case-insensitively
          const getHeader = (headers: Record<string, string> | undefined, key: string): string | undefined => {
            if (!headers) return undefined;
            const found = Object.entries(headers).find(([k]) => k.toLowerCase() === key.toLowerCase());
            return found ? String(found[1]) : undefined;
          };
    
          const reqContentType = getHeader(req.requestData.headers, 'content-type') || '';
          const respContentType = getHeader(req.responseData?.headers || {}, 'content-type') || '';
    
          // Build compact text response
          const lines: string[] = [];
    
          lines.push(`Request Details [${index}]:\n`);
          lines.push(`${req.method} ${req.url}`);
    
          if (req.status) {
            lines.push(`Status: ${req.status} ${req.statusText || 'OK'} (took ${req.timing}ms)`);
          } else {
            lines.push(`Status: Pending (no response yet)`);
          }
    
          // Calculate sizes
          const requestSize = req.requestData.postData
            ? req.requestData.postData.length
            : 0;
          const responseSize = req.responseData?.body
            ? req.responseData.body.length
            : 0;
    
          const formatBytes = (bytes: number) => {
            if (bytes === 0) return '0 bytes';
            if (bytes < 1024) return `${bytes} bytes`;
            return `${(bytes / 1024).toFixed(1)}KB`;
          };
    
          if (responseSize > 0 || requestSize > 0) {
            const reqPart = requestSize > 0 ? formatBytes(requestSize) : '0 bytes';
            const respPart = responseSize > 0 ? formatBytes(responseSize) : '0 bytes';
            lines.push(`Size: requestBody=${reqPart}, responseBody≈${respPart}`);
          }
    
          // Request headers (show important ones)
          const importantRequestHeaders = ['content-type', 'authorization', 'cookie', 'user-agent', 'accept'];
          const reqHeaders = Object.entries(req.requestData.headers)
            .filter(([key]) => importantRequestHeaders.includes(key.toLowerCase()));
    
          if (reqHeaders.length > 0) {
            lines.push('\nRequest Headers:');
            const order = (name: string) => {
              const idx = importantRequestHeaders.indexOf(name.toLowerCase());
              return idx === -1 ? importantRequestHeaders.length : idx;
            };
            reqHeaders
              .sort(([a], [b]) => {
                const oa = order(a);
                const ob = order(b);
                if (oa !== ob) return oa - ob;
                return a.localeCompare(b);
              })
              .forEach(([key, value]) => {
                const keyLower = key.toLowerCase();
                if (keyLower === 'authorization' || keyLower === 'cookie') {
                  if (!exposeSensitive) {
                    if (keyLower === 'authorization') {
                      const scheme = value.split(' ')[0] || '';
                      lines.push(`  ${key}: ${scheme ? `${scheme} <redacted>` : '<redacted>'}`);
                    } else {
                      lines.push(`  ${key}: <redacted>`);
                    }
                  } else {
                    const truncated = value.length > 60
                      ? value.substring(0, 57) + '...'
                      : value;
                    lines.push(`  ${key}: ${truncated}`);
                  }
                } else {
                  lines.push(`  ${key}: ${value}`);
                }
              });
          }
    
          // Request body
          if (req.requestData.postData) {
            lines.push('\nRequest Body:');
    
            // Mask passwords in JSON
            let displayData = req.requestData.postData;
            try {
              const parsed = JSON.parse(displayData);
              if (parsed.password) parsed.password = '***';
              if (parsed.pass) parsed.pass = '***';
              const compact = JSON.stringify(parsed);
              const pretty = JSON.stringify(parsed, null, 2);
              // Pretty-print small JSON bodies for readability; keep large ones compact
              displayData = pretty.length <= 500 ? pretty : compact;
            } catch (e) {
              // Not JSON, use as is
            }
    
            // Truncate at 500 chars
            if (displayData.length > 500) {
              const shown = 500;
              const remaining = displayData.length - shown;
              const coverage = Math.round((shown / displayData.length) * 1000) / 10;
              lines.push(`  ${displayData.substring(0, shown)}`);
              lines.push(`  ... [${remaining} more chars] (previewCoverage≈${coverage}%)`);
            } else {
              lines.push(`  ${displayData}`);
            }
          }
    
          // Response headers (show important ones)
          const importantResponseHeaders = ['content-type', 'set-cookie', 'cache-control', 'location', 'x-cache'];
          const respHeaders = req.responseData?.headers
            ? Object.entries(req.responseData.headers)
                .filter(([key]) => importantResponseHeaders.includes(key.toLowerCase()))
            : [];
    
          if (respHeaders.length > 0) {
            lines.push('\nResponse Headers:');
            const order = (name: string) => {
              const idx = importantResponseHeaders.indexOf(name.toLowerCase());
              return idx === -1 ? importantResponseHeaders.length : idx;
            };
            respHeaders
              .sort(([a], [b]) => {
                const oa = order(a);
                const ob = order(b);
                if (oa !== ob) return oa - ob;
                return a.localeCompare(b);
              })
              .forEach(([key, value]) => {
                const keyLower = key.toLowerCase();
                if (keyLower === 'set-cookie') {
                  if (!exposeSensitive) {
                    lines.push(`  ${key}: <redacted>`);
                  } else {
                    const truncated = value.length > 60
                      ? value.substring(0, 57) + '...'
                      : value;
                    lines.push(`  ${key}: ${truncated}`);
                  }
                } else {
                  lines.push(`  ${key}: ${value}`);
                }
              });
          }
    
          // Response body
          if (req.responseData?.body) {
            lines.push('\nResponse Body (truncated at 500 chars):');
    
            const rawBody = req.responseData.body;
            let displayBody = rawBody;
    
            // Pretty-print small JSON responses for readability; keep large ones compact
            if (respContentType.toLowerCase().includes('application/json')) {
              try {
                const parsed = JSON.parse(rawBody);
                const compact = JSON.stringify(parsed);
                const pretty = JSON.stringify(parsed, null, 2);
                displayBody = pretty.length <= 500 ? pretty : compact;
              } catch {
                // Not valid JSON, fall back to raw body
              }
            }
    
            if (displayBody.length > 500) {
              const shown = 500;
              const remaining = displayBody.length - shown;
              const coverage = Math.round((shown / displayBody.length) * 1000) / 10;
              lines.push(`  ${displayBody.substring(0, shown)}`);
              lines.push(`  ... [${remaining} more chars] (previewCoverage≈${coverage}%)`);
            } else {
              lines.push(`  ${displayBody}`);
            }
          } else if (req.status) {
            lines.push('\nResponse Body: (none or binary data)');
          }
    
          // If either request or response body was truncated, provide a confirm_output token
          const reqBody = req.requestData.postData || '';
          const respBody = req.responseData?.body || '';
          const reqTruncated = typeof reqBody === 'string' && reqBody.length > 500;
          const respTruncated = typeof respBody === 'string' && respBody.length > 500;
    
          if (reqTruncated || respTruncated) {
            const inferExt = (ct: string): string => {
              const c = (ct || '').toLowerCase();
              if (c.includes('application/json')) return 'json';
              if (c.includes('text/html')) return 'html';
              if (c.startsWith('text/')) return 'txt';
              if (c.includes('xml')) return 'xml';
              return 'txt';
            };
    
            const { getScreenshotsDir } = await import('../../../toolHandler.js');
            const baseInspectorDir = path.dirname(getScreenshotsDir());
            const outDir = path.join(baseInspectorDir, 'network-bodies');
    
            const makeSafe = (s: string) => s.replace(/[^a-zA-Z0-9._-]/g, '-');
            const urlObj = (() => { try { return new URL(req.url); } catch { return null as any; } })();
            const host = urlObj?.hostname ? makeSafe(urlObj.hostname) : 'unknown-host';
            const ts = new Date().toISOString().replace(/[:.]/g, '-');
    
            const thunk = async (): Promise<string> => {
              if (!fs.existsSync(outDir)) {
                fs.mkdirSync(outDir, { recursive: true });
              }
    
              const messages: string[] = [];
    
              if (respTruncated) {
                const ext = inferExt(respContentType);
                const respFile = path.join(outDir, `${ts}-${index}-${makeSafe(req.method)}-${host}.response.${ext}`);
                fs.writeFileSync(respFile, respBody, 'utf8');
                messages.push(`✓ Saved full response body to: ${path.relative(process.cwd(), respFile)} (${respBody.length} bytes${respContentType ? `, content-type: ${respContentType}` : ''})`);
              }
    
              if (reqTruncated) {
                const ext = inferExt(reqContentType);
                const reqFile = path.join(outDir, `${ts}-${index}-${makeSafe(req.method)}-${host}.request.${ext}`);
                fs.writeFileSync(reqFile, reqBody, 'utf8');
                messages.push(`✓ Saved full request body to: ${path.relative(process.cwd(), reqFile)} (${reqBody.length} bytes${reqContentType ? `, content-type: ${reqContentType}` : ''})`);
              }
    
              if (messages.length === 0) {
                messages.push('Nothing to save (no truncated text bodies).');
              } else {
                messages.push('');
                messages.push(`Paths above are relative to the current working directory: ${process.cwd()}`);
              }
    
              messages.push('');
              messages.push('Note: .mcp-web-inspector/ is recommended in .gitignore to avoid committing sensitive data.');
    
              return messages.join('\n');
            };
    
            const totalLen = (respTruncated ? respBody.length : 0) + (reqTruncated ? reqBody.length : 0);
            const preview = makeConfirmPreview(thunk, {
              counts: { totalLength: totalLen, shownLength: 500, truncated: true },
              previewLines: [
                'Large network body detected — preview shown above.',
                'Confirm to save full body to disk (token-efficient).',
                `Output directory: ${path.relative(process.cwd(), outDir)}`,
              ],
            });
    
            lines.push('');
            lines.push(...preview.lines);
          }
    
          return {
            content: [{
              type: "text",
              text: lines.join('\n')
            }],
            isError: false
          };
        });
      }
    }
  • GetRequestDetailsArgs interface – defines input schema with a single required 'index' (number) parameter.
    interface GetRequestDetailsArgs {
      index: number;
    }
  • getMetadata() static method returning the tool metadata, including name 'get_request_details', description, annotations (readOnly), and inputSchema (object with required 'index' of type number).
    static getMetadata(sessionConfig?: SessionConfig): ToolMetadata {
      return {
        name: "get_request_details",
        description: "[may return preview+token] Get detailed information about a specific network request by index (from list_network_requests). Returns request/response headers, body (truncated at 500 chars), timing, and size. Request bodies with passwords are automatically masked. If a request or response body exceeds 500 chars, includes a preview and a one-time confirm_output token that, when called, saves the full body to disk under ./.mcp-web-inspector/network-bodies/ and returns the file path(s). Essential for debugging API responses and investigating failed requests.",
        annotations: ANNOTATIONS.readOnly,
        inputSchema: {
          type: "object",
          properties: {
            index: {
              type: "number",
              description: "Index of the request from list_network_requests output (e.g., [0], [1], etc.)"
            }
          },
          required: ["index"],
        },
      };
    }
  • GetRequestDetailsTool is registered in the BROWSER_TOOL_CLASSES array (line 99) alongside ListNetworkRequestsTool under the 'Network' section.
    // Network (2)
    ListNetworkRequestsTool,
    GetRequestDetailsTool,
  • Import of GetRequestDetailsTool from './network/get_request_details.js' in the browser registration file.
    import { GetRequestDetailsTool } from './network/get_request_details.js';
  • Cross-reference: list_network_requests.ts outputs instruction telling users to 'Use get_request_details(index) for full info'.
    lines.push('\nUse get_request_details(index) for full info (indices are 0-based from this list)');
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses several behavioral details not covered by annotations: truncation at 500 chars, password masking, preview+token mechanism for saving full bodies, and the role of the confirm_output tool. Annotations only indicate readOnlyHint=true and openWorldHint=false, so the description adds substantial value.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured and concise, with each sentence adding value. It opens with an important hint about the preview+token, then covers main functionality, truncation, masking, token usage, and the use case. No redundant or unnecessary information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (1 parameter, no output schema, annotations present), the description is complete. It explains return values (headers, body, timing, size), special behaviors (masking, truncation, preview+token), and the use case for debugging. It adequately covers what an agent needs to know to use the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema covers the single parameter 'index' with 100% description coverage. The description adds context by explaining how to obtain the index from list_network_requests output and provides an example format, enhancing usability beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Get detailed information about a specific network request by index (from list_network_requests).' It specifies the verb ('Get detailed information'), the resource ('network request'), and the source of the index, effectively distinguishing it from sibling tools like list_network_requests.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear usage context: 'Essential for debugging API responses and investigating failed requests.' It implicitly links to list_network_requests as a prerequisite. While it does not explicitly state when not to use the tool or list alternatives, the context is well-defined and the tool's specificity makes misuse unlikely.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other 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/antonzherdev/mcp-web-inspector'

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