proxy_export_har
Export recorded proxy sessions to HAR format for analysis, supporting filtering by method, URL, status code, and time range to capture specific network traffic.
Instructions
Export a recorded session (or filtered subset) to HAR format.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| session_id | Yes | Session ID | |
| output_file | No | Output HAR file path | |
| include_bodies | No | Include body text when available | |
| method | No | ||
| hostname_contains | No | ||
| url_contains | No | ||
| status_code | No | ||
| from_ts | No | ||
| to_ts | No | ||
| text | No | ||
| sort | No | asc |
Implementation Reference
- src/state.ts:331-668 (handler)The proxyManager.exportSessionHar method acts as a wrapper that calls the SessionStore.exportHar method to perform the actual HAR export logic.
// ── ProxyManager ── let nextRuleId = 1; export class ProxyManager { private server: mockttp.Mockttp | null = null; private cert: CertificateInfo | null = null; private port: number | null = null; private _running = false; private readonly sessionStore = new SessionStore(); // Upstream proxy private globalUpstream: UpstreamProxyConfig | null = null; private hostUpstreams = new Map<string, UpstreamProxyConfig>(); // Interception rules private rules = new Map<string, InterceptionRule>(); // Traffic capture (ring buffer) private traffic: CapturedExchange[] = []; private pendingRequests = new Map<string, CapturedExchange>(); private pendingRawBodies = new Map<string, { requestBody?: Buffer }>(); // TLS fingerprinting private tlsMetadataCache = new Map<string, TlsClientMetadata>(); private serverTlsCapture: ServerTlsCapture | null = null; private _ja3SpoofConfig: FingerprintSpoofConfig | null = null; // ── Lifecycle ── async start(port?: number, options: ProxyStartOptions = {}): Promise<{ port: number; url: string; cert: CertificateInfo }> { if (this._running) { throw new Error("Proxy is already running. Stop it first."); } // Generate CA cert (once, reused across rebuilds) if (!this.cert) { const mockttp = await getMockttp(); const ca = await mockttp.generateCACertificate({ bits: 2048 }); const fingerprint = mockttp.generateSPKIFingerprint(ca.cert); this.cert = { key: ca.key, cert: ca.cert, fingerprint }; } this.port = port || 0; await this.buildAndStart(); this._running = true; this.port = this.server!.port; if (options.persistenceEnabled) { await this.sessionStore.startSession({ sessionName: options.sessionName, captureProfile: options.captureProfile, storageDir: options.storageDir, maxDiskMb: options.maxDiskMb, }); } return { port: this.server!.port, url: this.server!.url, cert: this.cert, }; } async stop(): Promise<void> { if (!this._running) { throw new Error("Proxy is not running."); } // Deactivate all interceptors before stopping the proxy await interceptorManager.deactivateAll().catch(() => {}); await cleanupTempCerts().catch(() => {}); if (this.server) { await this.server.stop(); this.server = null; } await this.sessionStore.stopSession().catch(() => {}); this._running = false; this.pendingRequests.clear(); this.pendingRawBodies.clear(); this.tlsMetadataCache.clear(); this.disableServerTls(); if (this._ja3SpoofConfig) { await shutdownSpoofContainer(); } } isRunning(): boolean { return this._running; } getPort(): number | null { return this.port; } getCert(): CertificateInfo | null { return this.cert; } getStatus(): object { return { running: this.isRunning(), port: this.port, url: this.server?.url ?? null, certFingerprint: this.cert?.fingerprint ?? null, globalUpstream: this.globalUpstream, hostUpstreams: Object.fromEntries(this.hostUpstreams), ruleCount: this.rules.size, trafficCount: this.traffic.length, persistence: this.sessionStore.getRuntimeStatus(), }; } // ── Upstream Proxy ── async setGlobalUpstream(config: UpstreamProxyConfig): Promise<void> { this.globalUpstream = config; if (this._running) await this.rebuildMockttpRules(); } async clearGlobalUpstream(): Promise<void> { this.globalUpstream = null; if (this._running) await this.rebuildMockttpRules(); } getGlobalUpstream(): UpstreamProxyConfig | null { return this.globalUpstream; } async setHostUpstream(hostname: string, config: UpstreamProxyConfig): Promise<void> { this.hostUpstreams.set(hostname, config); if (this._running) await this.rebuildMockttpRules(); } async removeHostUpstream(hostname: string): Promise<boolean> { const removed = this.hostUpstreams.delete(hostname); if (removed && this._running) await this.rebuildMockttpRules(); return removed; } getHostUpstreams(): Map<string, UpstreamProxyConfig> { return this.hostUpstreams; } // ── Interception Rules ── async addRule(rule: Omit<InterceptionRule, "id" | "hitCount" | "createdAt">): Promise<InterceptionRule> { const id = `rule_${nextRuleId++}`; const newRule: InterceptionRule = { ...rule, id, hitCount: 0, createdAt: Date.now(), }; this.rules.set(id, newRule); if (this._running) await this.rebuildMockttpRules(); return newRule; } async updateRule(id: string, updates: Partial<Omit<InterceptionRule, "id" | "hitCount" | "createdAt">>): Promise<InterceptionRule> { const rule = this.rules.get(id); if (!rule) throw new Error(`Rule '${id}' not found`); Object.assign(rule, updates); if (this._running) await this.rebuildMockttpRules(); return rule; } async removeRule(id: string): Promise<boolean> { const removed = this.rules.delete(id); if (removed && this._running) await this.rebuildMockttpRules(); return removed; } getRule(id: string): InterceptionRule | undefined { return this.rules.get(id); } listRules(): InterceptionRule[] { return [...this.rules.values()].sort((a, b) => a.priority - b.priority); } async enableRule(id: string): Promise<void> { const rule = this.rules.get(id); if (!rule) throw new Error(`Rule '${id}' not found`); rule.enabled = true; if (this._running) await this.rebuildMockttpRules(); } async disableRule(id: string): Promise<void> { const rule = this.rules.get(id); if (!rule) throw new Error(`Rule '${id}' not found`); rule.enabled = false; if (this._running) await this.rebuildMockttpRules(); } testRulesAgainstRequest( requestInput: { url: string } & Partial<Omit<RuleTestRequest, "url">>, options: RuleTestOptions = {}, ): RuleTestResult { const request = this.normalizeRuleTestRequest(requestInput); const includeDisabled = options.includeDisabled ?? true; const limitRules = options.limitRules; const allRules = this.listRules(); const candidates = includeDisabled ? allRules : allRules.filter((r) => r.enabled); const rules = (typeof limitRules === "number" && limitRules >= 0) ? candidates.slice(0, limitRules) : candidates; const results: RuleMatchEvaluation[] = rules.map((rule) => { const { matched, checks } = this.evaluateRuleMatcher(rule.matcher, request); const eligible = matched && rule.enabled; return { ruleId: rule.id, enabled: rule.enabled, priority: rule.priority, description: rule.description, matched, eligible, checks, }; }); const matchedCount = results.filter((r) => r.matched).length; const effectiveMatches = results.filter((r) => r.eligible); const winner = effectiveMatches[0] ?? null; const winnerRule = winner ? this.getRule(winner.ruleId) ?? null : null; return { request, includeDisabled, evaluatedCount: results.length, totalRules: allRules.length, results, matchedCount, effectiveMatchCount: effectiveMatches.length, effectiveWinner: winner && winnerRule ? { ruleId: winner.ruleId, priority: winner.priority, description: winner.description, handlerType: winnerRule.handler.type, } : null, }; } testRulesAgainstExchange(exchangeId: string, options: RuleTestOptions = {}): RuleTestResult { const exchange = this.getExchange(exchangeId); if (!exchange) { throw new Error(`Exchange '${exchangeId}' not found`); } return this.testRulesAgainstRequest({ method: exchange.request.method, url: exchange.request.url, hostname: exchange.request.hostname, path: exchange.request.path, headers: exchange.request.headers, body: exchange.request.bodyPreview, }, options); } // ── Traffic ── getTraffic(): CapturedExchange[] { return this.traffic; } getExchange(id: string): CapturedExchange | undefined { return this.traffic.find((t) => t.id === id); } searchTraffic(query: string): CapturedExchange[] { const q = query.toLowerCase(); return this.traffic.filter((t) => { if (t.request.url.toLowerCase().includes(q)) return true; if (t.request.bodyPreview.toLowerCase().includes(q)) return true; if (t.response?.bodyPreview.toLowerCase().includes(q)) return true; for (const v of Object.values(t.request.headers)) { if (v.toLowerCase().includes(q)) return true; } if (t.response) { for (const v of Object.values(t.response.headers)) { if (v.toLowerCase().includes(q)) return true; } } return false; }); } clearTraffic(): number { const count = this.traffic.length; this.traffic.length = 0; this.pendingRequests.clear(); this.pendingRawBodies.clear(); return count; } // ── Persistent Sessions ── isSessionPersistenceEnabled(): boolean { return this.sessionStore.isActive(); } getSessionCaptureProfile(): "preview" | "full" | null { return this.sessionStore.getActiveProfile(); } async startSession(options: SessionStartOptions = {}): Promise<SessionManifest> { return await this.sessionStore.startSession(options); } async stopSession(): Promise<SessionManifest | null> { return await this.sessionStore.stopSession(); } getSessionStatus(): object { return this.sessionStore.getRuntimeStatus(); } async listSessions(): Promise<Array<SessionManifest & { diskUsageMb: number }>> { return await this.sessionStore.listSessions(); } async getSession(sessionId: string): Promise<SessionManifest> { return await this.sessionStore.getSession(sessionId); } async querySession(sessionId: string, query: SessionQuery): Promise<SessionQueryResult> { return await this.sessionStore.querySession(sessionId, query); } async getSessionExchange( sessionId: string, opts: { seq?: number; exchangeId?: string; includeBody?: boolean }, ): Promise<{ index: SessionIndexEntry; record?: unknown }> { return await this.sessionStore.getSessionExchange(sessionId, opts); } async exportSessionHar( - src/tools/sessions.ts:316-331 (handler)The tool handler function for "proxy_export_har" which processes the input parameters and calls proxyManager.exportSessionHar.
async ({ session_id, output_file, include_bodies, method, hostname_contains, url_contains, status_code, from_ts, to_ts, text, sort }) => { try { const exported = await proxyManager.exportSessionHar(session_id, { outputFile: output_file, includeBodies: include_bodies, query: { method, hostnameContains: hostname_contains, urlContains: url_contains, statusCode: status_code, fromTs: from_ts, toTs: to_ts, text, sort, }, }); - src/tools/sessions.ts:300-315 (registration)Registration of the "proxy_export_har" MCP tool with its input schema definition.
server.tool( "proxy_export_har", "Export a recorded session (or filtered subset) to HAR format.", { session_id: z.string().describe("Session ID"), output_file: z.string().optional().describe("Output HAR file path"), include_bodies: z.boolean().optional().default(true).describe("Include body text when available"), method: z.string().optional(), hostname_contains: z.string().optional(), url_contains: z.string().optional(), status_code: z.number().optional(), from_ts: z.number().optional(), to_ts: z.number().optional(), text: z.string().optional(), sort: z.enum(["asc", "desc"]).optional().default("asc"), },