Skip to main content
Glama
network.ts10.2 kB
/** * Network monitoring tools for Firefox DevTools MCP * Provides network request inspection capabilities */ import { successResponse, errorResponse, jsonResponse, truncateHeaders, } from '../utils/response-helpers.js'; import type { McpToolResponse } from '../types/common.js'; // Tool definitions export const listNetworkRequestsTool = { name: 'list_network_requests', description: 'List network requests. Returns IDs for get_network_request.', inputSchema: { type: 'object' as const, properties: { limit: { type: 'number', description: 'Max requests (default: 50)', }, sinceMs: { type: 'number', description: 'Only last N ms', }, urlContains: { type: 'string', description: 'URL filter (case-insensitive)', }, method: { type: 'string', description: 'HTTP method filter', }, status: { type: 'number', description: 'Exact status code', }, statusMin: { type: 'number', description: 'Min status code', }, statusMax: { type: 'number', description: 'Max status code', }, isXHR: { type: 'boolean', description: 'XHR/fetch only', }, resourceType: { type: 'string', description: 'Resource type filter', }, sortBy: { type: 'string', enum: ['timestamp', 'duration', 'status'], description: 'Sort field (default: timestamp)', }, detail: { type: 'string', enum: ['summary', 'min', 'full'], description: 'Detail level (default: summary)', }, format: { type: 'string', enum: ['text', 'json'], description: 'Output format (default: text)', }, }, }, }; export const getNetworkRequestTool = { name: 'get_network_request', description: 'Get request details by ID. URL lookup as fallback.', inputSchema: { type: 'object' as const, properties: { id: { type: 'string', description: 'Request ID from list_network_requests', }, url: { type: 'string', description: 'URL fallback (may match multiple)', }, format: { type: 'string', enum: ['text', 'json'], description: 'Output format (default: text)', }, }, }, }; // Tool handlers export async function handleListNetworkRequests(args: unknown): Promise<McpToolResponse> { try { const { limit = 50, sinceMs, urlContains, method, status, statusMin, statusMax, isXHR, resourceType, sortBy = 'timestamp', detail = 'summary', format = 'text', } = (args as { limit?: number; sinceMs?: number; urlContains?: string; method?: string; status?: number; statusMin?: number; statusMax?: number; isXHR?: boolean; resourceType?: string; sortBy?: 'timestamp' | 'duration' | 'status'; detail?: 'summary' | 'min' | 'full'; format?: 'text' | 'json'; }) || {}; const { getFirefox } = await import('../index.js'); const firefox = await getFirefox(); let requests = await firefox.getNetworkRequests(); // Apply time filter if (sinceMs !== undefined) { const cutoffTime = Date.now() - sinceMs; requests = requests.filter((req) => req.timestamp && req.timestamp >= cutoffTime); } // Apply filters if (urlContains) { const urlLower = urlContains.toLowerCase(); requests = requests.filter((req) => req.url.toLowerCase().includes(urlLower)); } if (method) { const methodUpper = method.toUpperCase(); requests = requests.filter((req) => req.method.toUpperCase() === methodUpper); } if (status !== undefined) { requests = requests.filter((req) => req.status === status); } if (statusMin !== undefined) { requests = requests.filter((req) => req.status !== undefined && req.status >= statusMin); } if (statusMax !== undefined) { requests = requests.filter((req) => req.status !== undefined && req.status <= statusMax); } if (isXHR !== undefined) { requests = requests.filter((req) => req.isXHR === isXHR); } if (resourceType) { const typeLower = resourceType.toLowerCase(); requests = requests.filter((req) => req.resourceType?.toLowerCase() === typeLower); } // Sort requests if (sortBy === 'timestamp') { requests.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); } else if (sortBy === 'duration') { requests.sort((a, b) => (b.timings?.duration || 0) - (a.timings?.duration || 0)); } else if (sortBy === 'status') { requests.sort((a, b) => (a.status || 0) - (b.status || 0)); } // Apply limit const limitedRequests = requests.slice(0, limit); const hasMore = requests.length > limit; // Format output based on detail level and format if (format === 'json') { // JSON format - return structured data based on detail level const responseData: any = { total: requests.length, showing: limitedRequests.length, hasMore, requests: [], }; if (detail === 'summary' || detail === 'min') { responseData.requests = limitedRequests.map((req) => ({ id: req.id, url: req.url, method: req.method, status: req.status, statusText: req.statusText, resourceType: req.resourceType, isXHR: req.isXHR, duration: req.timings?.duration, })); } else { // Full detail - apply header truncation to prevent token overflow responseData.requests = limitedRequests.map((req) => ({ id: req.id, url: req.url, method: req.method, status: req.status, statusText: req.statusText, resourceType: req.resourceType, isXHR: req.isXHR, timings: req.timings || null, requestHeaders: truncateHeaders(req.requestHeaders), responseHeaders: truncateHeaders(req.responseHeaders), })); } return jsonResponse(responseData); } // Text format (default) if (detail === 'summary') { const formattedRequests = limitedRequests.map((req) => { const statusInfo = req.status ? `[${req.status}${req.statusText ? ' ' + req.statusText : ''}]` : '[pending]'; return `${req.id} | ${req.method} ${req.url} ${statusInfo}${req.isXHR ? ' (XHR)' : ''}`; }); const header = `📡 ${requests.length} requests${hasMore ? ` (limit ${limit})` : ''}\n`; return successResponse(header + formattedRequests.join('\n')); } else if (detail === 'min') { // Compact JSON const minData = limitedRequests.map((req) => ({ id: req.id, url: req.url, method: req.method, status: req.status, statusText: req.statusText, resourceType: req.resourceType, isXHR: req.isXHR, duration: req.timings?.duration, })); return successResponse( `📡 ${requests.length} requests${hasMore ? ` (limit ${limit})` : ''}\n` + JSON.stringify(minData, null, 2) ); } else { // Full JSON including headers - apply truncation to prevent token overflow const fullData = limitedRequests.map((req) => ({ id: req.id, url: req.url, method: req.method, status: req.status, statusText: req.statusText, resourceType: req.resourceType, isXHR: req.isXHR, timings: req.timings || null, requestHeaders: truncateHeaders(req.requestHeaders), responseHeaders: truncateHeaders(req.responseHeaders), })); return successResponse( `📡 ${requests.length} requests${hasMore ? ` (limit ${limit})` : ''}\n` + JSON.stringify(fullData, null, 2) ); } } catch (error) { return errorResponse(error instanceof Error ? error : new Error(String(error))); } } export async function handleGetNetworkRequest(args: unknown): Promise<McpToolResponse> { try { const { id, url, format = 'text', } = args as { id?: string; url?: string; format?: 'text' | 'json' }; if (!id && !url) { return errorResponse('id or url required'); } const { getFirefox } = await import('../index.js'); const firefox = await getFirefox(); const requests = await firefox.getNetworkRequests(); let request = null; // Primary path: lookup by ID if (id) { request = requests.find((req) => req.id === id); if (!request) { return errorResponse(`ID ${id} not found`); } } else if (url) { // Fallback: lookup by URL (with collision detection) const matches = requests.filter((req) => req.url === url); if (matches.length === 0) { return errorResponse(`URL not found: ${url}`); } if (matches.length > 1) { const ids = matches.map((req) => req.id).join(', '); return errorResponse(`Multiple matches, use id: ${ids}`); } request = matches[0]; } if (!request) { return errorResponse('Request not found'); } // Format request details - apply header truncation to prevent token overflow const details = { id: request.id, url: request.url, method: request.method, status: request.status ?? null, statusText: request.statusText ?? null, resourceType: request.resourceType ?? null, isXHR: request.isXHR ?? false, timestamp: request.timestamp ?? null, timings: request.timings ?? null, requestHeaders: truncateHeaders(request.requestHeaders), responseHeaders: truncateHeaders(request.responseHeaders), }; if (format === 'json') { return jsonResponse(details); } return successResponse(JSON.stringify(details, null, 2)); } catch (error) { return errorResponse(error instanceof Error ? error : new Error(String(error))); } }

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/freema/firefox-devtools-mcp'

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