network-intelligence-tools.js•15.2 kB
/**
* Network Intelligence Tools for WebSee MCP Server
*
* Provides detailed network request analysis, timing, headers, and source tracing
* for debugging and performance optimization.
*
* @module network-intelligence-tools
*/
import { z } from 'zod';
import { SourceIntelligenceLayer } from '../index.js';
// ==================== Zod Schemas ====================
export const NetworkGetRequestsSchema = z.object({
url: z.string().url().describe('The page URL to analyze'),
waitTime: z
.number()
.optional()
.default(3000)
.describe('Time to wait for requests to complete (ms)'),
});
export const NetworkGetByUrlSchema = z.object({
url: z.string().url().describe('The page URL'),
pattern: z.string().describe("URL pattern to filter (e.g., '/api/*', '*.json')"),
});
export const NetworkGetTimingSchema = z.object({
url: z.string().url().describe('The page URL'),
requestUrl: z.string().describe('The specific request URL to get timing for'),
});
export const NetworkTraceInitiatorSchema = z.object({
url: z.string().url().describe('The page URL'),
requestUrl: z.string().describe('The specific request URL to trace'),
});
export const NetworkGetHeadersSchema = z.object({
url: z.string().url().describe('The page URL'),
requestUrl: z.string().describe('The specific request URL to get headers for'),
});
export const NetworkGetBodySchema = z.object({
url: z.string().url().describe('The page URL'),
requestUrl: z.string().describe('The specific request URL to get body for'),
});
// ==================== Helper Functions ====================
/**
* Enhanced page initialization with network tracking
*/
async function initializePageWithNetworkTracking(page, url, waitTime = 3000) {
const intelligence = new SourceIntelligenceLayer();
await intelligence.initialize(page);
const requestMap = new Map();
const responseMap = new Map();
// Track all network requests with enhanced data
page.on('request', request => {
requestMap.set(request.url(), {
url: request.url(),
method: request.method(),
headers: request.headers(),
postData: request.postData(),
resourceType: request.resourceType(),
timestamp: Date.now(),
});
});
page.on('response', async (response) => {
const request = response.request();
const timing = request.timing();
responseMap.set(response.url(), {
url: response.url(),
status: response.status(),
statusText: response.statusText(),
headers: response.headers(),
timing: timing,
});
});
await page.goto(url, { waitUntil: 'networkidle' });
await page.waitForTimeout(waitTime);
return { intelligence, requestMap, responseMap };
}
/**
* Match request URL using pattern (supports wildcards)
*/
function matchesPattern(url, pattern) {
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
return regex.test(url);
}
/**
* Calculate detailed timing metrics from Playwright timing object
*/
function calculateTiming(timing) {
return {
dns: timing.domainLookupEnd - timing.domainLookupStart,
connect: timing.connectEnd - timing.connectStart,
ssl: timing.secureConnectionStart > 0 ? timing.connectEnd - timing.secureConnectionStart : 0,
ttfb: timing.responseStart - timing.requestStart,
download: timing.responseEnd - timing.responseStart,
total: timing.responseEnd - timing.requestStart,
};
}
/**
* Parse stack trace to extract source locations
*/
function parseStackTrace(stackTrace) {
return stackTrace
.map(line => {
const match = line.match(/at\s+(?:(.+?)\s+)?\(?(.+?):(\d+):(\d+)\)?/);
if (match) {
const [, func, file, lineStr, colStr] = match;
return {
function: func?.trim() || 'anonymous',
file: file.trim(),
line: parseInt(lineStr),
column: parseInt(colStr),
};
}
return null;
})
.filter((item) => item !== null);
}
// ==================== Tool Implementations ====================
/**
* Get all network requests for a page
*/
export async function networkGetRequests(page, params) {
const { intelligence, requestMap, responseMap } = await initializePageWithNetworkTracking(page, params.url, params.waitTime);
const traces = intelligence.getNetworkTraces();
const requests = [];
// Combine data from intelligence layer and Playwright
for (const trace of traces) {
const requestData = requestMap.get(trace.url);
const responseData = responseMap.get(trace.url);
requests.push({
url: trace.url,
method: trace.method,
status: trace.status || responseData?.status,
duration: trace.duration,
size: responseData?.headers?.['content-length']
? parseInt(responseData.headers['content-length'])
: undefined,
timestamp: trace.timestamp,
initiator: trace.initiator,
stackTrace: trace.stackTrace,
requestHeaders: requestData?.headers,
responseHeaders: responseData?.headers,
});
}
return { requests };
}
/**
* Filter network requests by URL pattern
*/
export async function networkGetByUrl(page, params) {
const { requests } = await networkGetRequests(page, {
url: params.url,
waitTime: 3000,
});
const filteredRequests = requests.filter(req => matchesPattern(req.url, params.pattern));
return {
requests: filteredRequests.map(req => ({
url: req.url,
method: req.method,
status: req.status,
duration: req.duration,
timestamp: req.timestamp,
initiator: req.initiator,
})),
};
}
/**
* Get detailed timing information for a specific request
*/
export async function networkGetTiming(page, params) {
const timingMap = new Map();
page.on('response', async (response) => {
if (response.url() === params.requestUrl) {
const request = response.request();
timingMap.set(response.url(), request.timing());
}
});
await page.goto(params.url, { waitUntil: 'networkidle' });
await page.waitForTimeout(2000);
const timing = timingMap.get(params.requestUrl);
if (!timing) {
return {
error: `Request not found: ${params.requestUrl}`,
};
}
return calculateTiming(timing);
}
/**
* Trace network request to its source code origin
*/
export async function networkTraceInitiator(page, params) {
const intelligence = new SourceIntelligenceLayer();
await intelligence.initialize(page);
await page.goto(params.url, { waitUntil: 'networkidle' });
await page.waitForTimeout(2000);
const traces = intelligence.getNetworkTraces();
const targetTrace = traces.find(t => t.url === params.requestUrl);
if (!targetTrace) {
return {
error: `Request not found: ${params.requestUrl}`,
};
}
if (!targetTrace.stackTrace || targetTrace.stackTrace.length === 0) {
return {
error: 'No stack trace available for this request',
};
}
// Parse the first stack frame as the immediate initiator
const parsedStack = parseStackTrace(targetTrace.stackTrace);
if (parsedStack.length === 0) {
return {
error: 'Unable to parse stack trace',
};
}
const initiator = parsedStack[0];
return {
file: initiator.file,
line: initiator.line,
column: initiator.column,
function: initiator.function,
stackTrace: parsedStack,
};
}
/**
* Get request and response headers for a specific request
*/
export async function networkGetHeaders(page, params) {
let requestHeaders = {};
let responseHeaders = {};
let found = false;
page.on('request', request => {
if (request.url() === params.requestUrl) {
requestHeaders = request.headers();
found = true;
}
});
page.on('response', response => {
if (response.url() === params.requestUrl) {
responseHeaders = response.headers();
found = true;
}
});
await page.goto(params.url, { waitUntil: 'networkidle' });
await page.waitForTimeout(2000);
if (!found) {
return {
error: `Request not found: ${params.requestUrl}`,
};
}
return {
requestHeaders,
responseHeaders,
};
}
/**
* Get request and response body for a specific request
*/
export async function networkGetBody(page, params) {
let requestBody = null;
let responseBody = null;
let contentType = '';
let found = false;
page.on('request', request => {
if (request.url() === params.requestUrl) {
requestBody = request.postData() || null;
found = true;
}
});
page.on('response', async (response) => {
if (response.url() === params.requestUrl) {
try {
const buffer = await response.body();
responseBody = buffer.toString('utf-8');
contentType = response.headers()['content-type'] || '';
found = true;
}
catch (error) {
// Response body might not be available for some requests
responseBody = null;
}
}
});
await page.goto(params.url, { waitUntil: 'networkidle' });
await page.waitForTimeout(2000);
if (!found) {
return {
error: `Request not found: ${params.requestUrl}`,
};
}
return {
requestBody,
responseBody,
contentType,
};
}
// ==================== Tool Definitions for MCP ====================
/**
* Tool definitions compatible with MCP server
*/
export const networkIntelligenceTools = [
{
name: 'network_get_requests',
description: 'Get all network requests made by a page with detailed information',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'The page URL to analyze',
},
waitTime: {
type: 'number',
description: 'Time to wait for requests to complete (ms)',
default: 3000,
},
},
required: ['url'],
},
},
{
name: 'network_get_by_url',
description: 'Filter network requests by URL pattern (supports wildcards)',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'The page URL',
},
pattern: {
type: 'string',
description: "URL pattern to filter (e.g., '/api/*', '*.json')",
},
},
required: ['url', 'pattern'],
},
},
{
name: 'network_get_timing',
description: 'Get detailed timing metrics for a specific network request',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'The page URL',
},
requestUrl: {
type: 'string',
description: 'The specific request URL to get timing for',
},
},
required: ['url', 'requestUrl'],
},
},
{
name: 'network_trace_initiator',
description: 'Trace a network request to its source code origin',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'The page URL',
},
requestUrl: {
type: 'string',
description: 'The specific request URL to trace',
},
},
required: ['url', 'requestUrl'],
},
},
{
name: 'network_get_headers',
description: 'Get request and response headers for a specific network request',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'The page URL',
},
requestUrl: {
type: 'string',
description: 'The specific request URL to get headers for',
},
},
required: ['url', 'requestUrl'],
},
},
{
name: 'network_get_body',
description: 'Get request and response body for a specific network request',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'The page URL',
},
requestUrl: {
type: 'string',
description: 'The specific request URL to get body for',
},
},
required: ['url', 'requestUrl'],
},
},
];
// ==================== Tool Handler Factory ====================
/**
* Create a tool handler for the MCP server
*/
export function createNetworkToolHandler(page) {
return async (toolName, args) => {
switch (toolName) {
case 'network_get_requests': {
const params = NetworkGetRequestsSchema.parse(args);
return await networkGetRequests(page, params);
}
case 'network_get_by_url': {
const params = NetworkGetByUrlSchema.parse(args);
return await networkGetByUrl(page, params);
}
case 'network_get_timing': {
const params = NetworkGetTimingSchema.parse(args);
return await networkGetTiming(page, params);
}
case 'network_trace_initiator': {
const params = NetworkTraceInitiatorSchema.parse(args);
return await networkTraceInitiator(page, params);
}
case 'network_get_headers': {
const params = NetworkGetHeadersSchema.parse(args);
return await networkGetHeaders(page, params);
}
case 'network_get_body': {
const params = NetworkGetBodySchema.parse(args);
return await networkGetBody(page, params);
}
default:
throw new Error(`Unknown network tool: ${toolName}`);
}
};
}
// ==================== Exports ====================
// All exports are already defined above with their declarations
//# sourceMappingURL=network-intelligence-tools.js.map