Skip to main content
Glama

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
NameRequiredDescriptionDefault
session_idYesSession ID
output_fileNoOutput HAR file path
include_bodiesNoInclude body text when available
methodNo
hostname_containsNo
url_containsNo
status_codeNo
from_tsNo
to_tsNo
textNo
sortNoasc

Implementation Reference

  • 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(
  • 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,
          },
        });
  • 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"),
      },
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/yfe404/proxy-mcp'

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