Skip to main content
Glama

WebSee MCP Server

by 1AQuantum
network-tracer.ts7.97 kB
/** * Network Tracer Agent * Intercepts network requests and captures stack traces for source intelligence * Part of the WebSee Source Intelligence Layer */ import { Page } from 'playwright'; // Extend XMLHttpRequest interface to include custom tracking properties declare global { interface XMLHttpRequest { __websee_url?: string; __websee_method?: string; __websee_stack?: string[]; } } interface NetworkTrace { url: string; method: string; timestamp: number; stackTrace: string[]; initiator: { type: 'fetch' | 'xhr' | 'script' | 'parser' | 'other'; lineNumber?: number; columnNumber?: number; url?: string; }; requestHeaders?: Record<string, string>; responseHeaders?: Record<string, string>; status?: number; duration?: number; } export class NetworkTracer { private traces: Map<string, NetworkTrace> = new Map(); /** * Initialize the network tracer with a Playwright page */ async initialize(page: Page): Promise<void> { // Inject network interception script into the page await page.addInitScript(() => { // Store original functions const originalFetch = window.fetch; const originalXHROpen = XMLHttpRequest.prototype.open; const originalXHRSend = XMLHttpRequest.prototype.send; // Helper to capture stack trace const captureStackTrace = (): string[] => { const stack = new Error().stack || ''; return stack .split('\n') .slice(2) // Remove Error line and captureStackTrace line .filter(line => !line.includes('NetworkTracer')) .map(line => line.trim()) .slice(0, 10); // Limit to 10 frames }; // Intercept fetch window.fetch = async function (...args) { const startTime = Date.now(); const stackTrace = captureStackTrace(); const url = args[0] instanceof Request ? args[0].url : String(args[0]); const method = (args[1]?.method || 'GET').toUpperCase(); // Emit custom event for tracking window.dispatchEvent( new CustomEvent('__websee_network_start', { detail: { type: 'fetch', url, method, stackTrace, timestamp: startTime, }, }) ); try { const response = await originalFetch.apply(this, args); const duration = Date.now() - startTime; const headers: Record<string, string> = {}; response.headers.forEach((value, key) => { headers[key] = value; }); window.dispatchEvent( new CustomEvent('__websee_network_complete', { detail: { url, status: response.status, duration, headers, }, }) ); return response; } catch (error) { window.dispatchEvent( new CustomEvent('__websee_network_error', { detail: { url, error: String(error) }, }) ); throw error; } }; // Intercept XMLHttpRequest XMLHttpRequest.prototype.open = function ( method: string, url: string, async?: boolean, username?: string | null, password?: string | null ) { this.__websee_url = url; this.__websee_method = method; this.__websee_stack = captureStackTrace(); return originalXHROpen.call( this, method, url, async !== undefined ? async : true, username, password ); }; XMLHttpRequest.prototype.send = function (...args) { const startTime = Date.now(); window.dispatchEvent( new CustomEvent('__websee_network_start', { detail: { type: 'xhr', url: this.__websee_url, method: this.__websee_method, stackTrace: this.__websee_stack, timestamp: startTime, }, }) ); this.addEventListener('loadend', () => { const duration = Date.now() - startTime; window.dispatchEvent( new CustomEvent('__websee_network_complete', { detail: { url: this.__websee_url, status: this.status, duration, }, }) ); }); return originalXHRSend.apply(this, args); }; }); // Listen for network events await page.exposeFunction('__websee_network_handler', (event: any) => { this.handleNetworkEvent(event); }); await page.addInitScript(() => { ['__websee_network_start', '__websee_network_complete', '__websee_network_error'].forEach( eventName => { window.addEventListener(eventName, (e: any) => { (window as any).__websee_network_handler(e.detail); }); } ); }); } /** * Handle network events from the page */ private handleNetworkEvent(event: any): void { if (!event.url) return; const key = `${event.method}_${event.url}_${event.timestamp}`; if (event.stackTrace) { // Start of request this.traces.set(key, { url: event.url, method: event.method, timestamp: event.timestamp, stackTrace: event.stackTrace, initiator: { type: event.type, // Parse stack trace for source location ...this.parseStackLocation(event.stackTrace[0]), }, }); } else if (event.status !== undefined) { // Completion of request const trace = Array.from(this.traces.values()).find(t => t.url === event.url && !t.status); if (trace) { trace.status = event.status; trace.duration = event.duration; if (event.headers) { trace.responseHeaders = event.headers; } } } } /** * Parse stack trace line to extract source location */ private parseStackLocation(stackLine: string): { url?: string; lineNumber?: number; columnNumber?: number; } { const match = stackLine.match(/at .* \(?(.*):(\d+):(\d+)\)?$/); if (match) { return { url: match[1], lineNumber: parseInt(match[2]), columnNumber: parseInt(match[3]), }; } return {}; } /** * Get all network traces */ getTraces(): NetworkTrace[] { return Array.from(this.traces.values()); } /** * Get traces for a specific URL pattern */ getTracesForUrl(urlPattern: string | RegExp): NetworkTrace[] { const pattern = typeof urlPattern === 'string' ? new RegExp(urlPattern.replace(/\*/g, '.*')) : urlPattern; return this.getTraces().filter(trace => pattern.test(trace.url)); } /** * Clear all traces */ clearTraces(): void { this.traces.clear(); } /** * Get a summary of network activity */ getSummary(): { totalRequests: number; byMethod: Record<string, number>; byStatus: Record<number, number>; averageDuration: number; } { const traces = this.getTraces(); const byMethod: Record<string, number> = {}; const byStatus: Record<number, number> = {}; let totalDuration = 0; let completedCount = 0; traces.forEach(trace => { byMethod[trace.method] = (byMethod[trace.method] || 0) + 1; if (trace.status) { byStatus[trace.status] = (byStatus[trace.status] || 0) + 1; } if (trace.duration) { totalDuration += trace.duration; completedCount++; } }); return { totalRequests: traces.length, byMethod, byStatus, averageDuration: completedCount > 0 ? totalDuration / completedCount : 0, }; } /** * Cleanup and destroy the tracer */ async destroy(): Promise<void> { this.traces.clear(); } }

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/1AQuantum/websee-mcp-server'

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