Skip to main content
Glama

proxy_mobile_setup

Set up mobile device capture: start proxy listeners, optionally inject CA on Android, and generate a script to route AP traffic through the proxy.

Instructions

One-command mobile capture: start explicit + transparent listeners, optionally inject the CA on an Android device, and emit a sudo-runnable script that wires iptables/sysctl/nmcli on the AP iface. Designed to pair with the proxy-ap-card firmware (ESP32-S3 rogue AP over USB-NCM).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
ap_ifaceNoAP/USB interface name. Auto-detected via cdc_ncm driver if omitted.
ap_addressNoLaptop-side address on the AP iface (default: 192.168.99.2/24, matches proxy-ap-card firmware).192.168.99.2/24
ap_subnetNoSubnet the AP serves to clients (default: 192.168.4.0/24, matches proxy-ap-card firmware).192.168.4.0/24
egress_ifaceNoHost's internet-facing iface. Auto-detected from /proc/net/route if omitted.
explicit_portNoPort for the explicit HTTP proxy (default: 8080).
transparent_portNoPort for the transparent HTTPS listener (default: 8443).
block_quicNoDrop UDP/443 on the AP iface so apps fall back to TCP/TLS (capturable). Default: true.
upstream_proxy_urlNoOptional upstream proxy URL (socks5://user:pass@host:port or http://...). Sets the global upstream for BOTH listeners.
android_serialNoADB serial of an Android device to inject the CA on. If omitted, no cert injection is attempted.
inject_certNoInject the CA into the Android device's system store. Ignored if android_serial is omitted.

Implementation Reference

  • Main handler function for proxy_mobile_setup tool. Detects AP iface, starts explicit + transparent listeners, optionally injects CA on Android device, emits a sudo-runnable iptables script, and returns setup summary with next steps.
    async ({
      ap_iface,
      ap_address,
      ap_subnet,
      egress_iface,
      explicit_port,
      transparent_port,
      block_quic,
      upstream_proxy_url,
      android_serial,
      inject_cert,
    }) => {
      try {
        // 1. Resolve AP iface.
        let apIface = ap_iface;
        let ifaceReason = "user-provided";
        if (!apIface) {
          const detected = detectApIface();
          if (!detected) {
            return { content: [{ type: "text", text: JSON.stringify({
              status: "error",
              error: "Could not auto-detect an AP interface. Plug in the proxy-ap-card (or another cdc_ncm USB NCM device) and retry, or pass ap_iface explicitly. Known interfaces:",
              interfaces: readdirSync("/sys/class/net").filter((n) => n !== "lo"),
            }) }] };
          }
          apIface = detected.iface;
          ifaceReason = detected.reason;
        }
    
        // 2. Resolve egress.
        const egressIface = egress_iface ?? detectEgressIface();
    
        // 3. Explicit proxy.
        let explicitPortUsed = explicit_port;
        if (!proxyManager.isRunning()) {
          const started = await proxyManager.start(explicit_port);
          explicitPortUsed = started.port;
        } else {
          explicitPortUsed = proxyManager.getPort() ?? explicit_port;
        }
    
        // 4. Transparent listener.
        let transparentPortUsed = transparent_port;
        if (!proxyManager.isTransparentRunning()) {
          const startedTp = await proxyManager.startTransparent(transparent_port);
          transparentPortUsed = startedTp.port;
        } else {
          transparentPortUsed = proxyManager.getTransparentPort() ?? transparent_port;
        }
    
        // 5. Upstream (optional).
        let upstreamSet = false;
        if (upstream_proxy_url) {
          await proxyManager.setGlobalUpstream({ proxyUrl: upstream_proxy_url });
          upstreamSet = true;
        }
    
        // 6. Android CA injection (optional).
        let certInjected = false;
        let androidTargetId: string | null = null;
        if (android_serial && inject_cert) {
          const cert = proxyManager.getCert();
          if (!cert) throw new Error("Proxy started but no cert was generated — this is a bug.");
          const result = await interceptorManager.activate("android-adb", {
            proxyPort: explicitPortUsed,
            certPem: cert.cert,
            certFingerprint: cert.fingerprint,
            serial: android_serial,
            injectCert: true,
            setupTunnel: false,  // AP + iptables handle routing; no reverse tunnel needed
            setWifiProxy: false, // same reason
          });
          certInjected = true;
          androidTargetId = result.targetId;
        }
    
        // 7. Emit sudo script.
        const scriptPath = writeScript(
          buildSetupScript({
            apIface,
            apAddress: ap_address,
            apSubnet: ap_subnet,
            egressIface,
            explicitPort: explicitPortUsed,
            transparentPort: transparentPortUsed,
            blockQuic: block_quic,
          }),
          "mobile-setup",
        );
    
        return {
          content: [{
            type: "text",
            text: JSON.stringify({
              status: "success",
              ap_iface: apIface,
              ap_iface_reason: ifaceReason,
              ap_address,
              ap_subnet,
              egress_iface: egressIface,
              explicit_port: explicitPortUsed,
              transparent_port: transparentPortUsed,
              block_quic,
              upstream_set: upstreamSet,
              cert_injected: certInjected,
              android_target_id: androidTargetId,
              sudo_script: scriptPath,
              sudo_command: `sudo bash ${scriptPath}`,
              next_steps: [
                `Run:  sudo bash ${scriptPath}`,
                "Connect the target device to the proxy-ap WiFi network (credentials live in the proxy-ap-card firmware).",
                "Call proxy_list_traffic — transparent HTTPS exchanges appear with source=\"transparent\", HTTP with source=\"explicit\".",
                "When done, call proxy_mobile_teardown and run the emitted sudo script.",
              ],
            }),
          }],
        };
      } catch (e) {
        return { content: [{ type: "text", text: JSON.stringify({ status: "error", error: errorToString(e) }) }] };
      }
    },
  • Zod schema definitions for proxy_mobile_setup input parameters including ap_iface, ap_address, ap_subnet, egress_iface, explicit_port, transparent_port, block_quic, upstream_proxy_url, android_serial, and inject_cert.
    {
      ap_iface: z.string().optional().describe("AP/USB interface name. Auto-detected via cdc_ncm driver if omitted."),
      ap_address: z.string().optional().default("192.168.99.2/24").describe("Laptop-side address on the AP iface (default: 192.168.99.2/24, matches proxy-ap-card firmware)."),
      ap_subnet: z.string().optional().default("192.168.4.0/24").describe("Subnet the AP serves to clients (default: 192.168.4.0/24, matches proxy-ap-card firmware)."),
      egress_iface: z.string().optional().describe("Host's internet-facing iface. Auto-detected from /proc/net/route if omitted."),
      explicit_port: z.number().optional().default(8080).describe("Port for the explicit HTTP proxy (default: 8080)."),
      transparent_port: z.number().optional().default(8443).describe("Port for the transparent HTTPS listener (default: 8443)."),
      block_quic: z.boolean().optional().default(true).describe("Drop UDP/443 on the AP iface so apps fall back to TCP/TLS (capturable). Default: true."),
      upstream_proxy_url: z.string().optional().describe("Optional upstream proxy URL (socks5://user:pass@host:port or http://...). Sets the global upstream for BOTH listeners."),
      android_serial: z.string().optional().describe("ADB serial of an Android device to inject the CA on. If omitted, no cert injection is attempted."),
      inject_cert: z.boolean().optional().default(true).describe("Inject the CA into the Android device's system store. Ignored if android_serial is omitted."),
    },
  • Registration function registerMobileTools that calls server.tool('proxy_mobile_setup', ...) to register the tool with the McpServer, including its description, schema, and async handler.
    export function registerMobileTools(server: McpServer): void {
      server.tool(
        "proxy_mobile_setup",
        "One-command mobile capture: start explicit + transparent listeners, optionally inject the CA on an Android device, and emit a sudo-runnable script that wires iptables/sysctl/nmcli on the AP iface. Designed to pair with the proxy-ap-card firmware (ESP32-S3 rogue AP over USB-NCM).",
        {
          ap_iface: z.string().optional().describe("AP/USB interface name. Auto-detected via cdc_ncm driver if omitted."),
          ap_address: z.string().optional().default("192.168.99.2/24").describe("Laptop-side address on the AP iface (default: 192.168.99.2/24, matches proxy-ap-card firmware)."),
          ap_subnet: z.string().optional().default("192.168.4.0/24").describe("Subnet the AP serves to clients (default: 192.168.4.0/24, matches proxy-ap-card firmware)."),
          egress_iface: z.string().optional().describe("Host's internet-facing iface. Auto-detected from /proc/net/route if omitted."),
          explicit_port: z.number().optional().default(8080).describe("Port for the explicit HTTP proxy (default: 8080)."),
          transparent_port: z.number().optional().default(8443).describe("Port for the transparent HTTPS listener (default: 8443)."),
          block_quic: z.boolean().optional().default(true).describe("Drop UDP/443 on the AP iface so apps fall back to TCP/TLS (capturable). Default: true."),
          upstream_proxy_url: z.string().optional().describe("Optional upstream proxy URL (socks5://user:pass@host:port or http://...). Sets the global upstream for BOTH listeners."),
          android_serial: z.string().optional().describe("ADB serial of an Android device to inject the CA on. If omitted, no cert injection is attempted."),
          inject_cert: z.boolean().optional().default(true).describe("Inject the CA into the Android device's system store. Ignored if android_serial is omitted."),
        },
        async ({
          ap_iface,
          ap_address,
          ap_subnet,
          egress_iface,
          explicit_port,
          transparent_port,
          block_quic,
          upstream_proxy_url,
          android_serial,
          inject_cert,
        }) => {
          try {
            // 1. Resolve AP iface.
            let apIface = ap_iface;
            let ifaceReason = "user-provided";
            if (!apIface) {
              const detected = detectApIface();
              if (!detected) {
                return { content: [{ type: "text", text: JSON.stringify({
                  status: "error",
                  error: "Could not auto-detect an AP interface. Plug in the proxy-ap-card (or another cdc_ncm USB NCM device) and retry, or pass ap_iface explicitly. Known interfaces:",
                  interfaces: readdirSync("/sys/class/net").filter((n) => n !== "lo"),
                }) }] };
              }
              apIface = detected.iface;
              ifaceReason = detected.reason;
            }
    
            // 2. Resolve egress.
            const egressIface = egress_iface ?? detectEgressIface();
    
            // 3. Explicit proxy.
            let explicitPortUsed = explicit_port;
            if (!proxyManager.isRunning()) {
              const started = await proxyManager.start(explicit_port);
              explicitPortUsed = started.port;
            } else {
              explicitPortUsed = proxyManager.getPort() ?? explicit_port;
            }
    
            // 4. Transparent listener.
            let transparentPortUsed = transparent_port;
            if (!proxyManager.isTransparentRunning()) {
              const startedTp = await proxyManager.startTransparent(transparent_port);
              transparentPortUsed = startedTp.port;
            } else {
              transparentPortUsed = proxyManager.getTransparentPort() ?? transparent_port;
            }
    
            // 5. Upstream (optional).
            let upstreamSet = false;
            if (upstream_proxy_url) {
              await proxyManager.setGlobalUpstream({ proxyUrl: upstream_proxy_url });
              upstreamSet = true;
            }
    
            // 6. Android CA injection (optional).
            let certInjected = false;
            let androidTargetId: string | null = null;
            if (android_serial && inject_cert) {
              const cert = proxyManager.getCert();
              if (!cert) throw new Error("Proxy started but no cert was generated — this is a bug.");
              const result = await interceptorManager.activate("android-adb", {
                proxyPort: explicitPortUsed,
                certPem: cert.cert,
                certFingerprint: cert.fingerprint,
                serial: android_serial,
                injectCert: true,
                setupTunnel: false,  // AP + iptables handle routing; no reverse tunnel needed
                setWifiProxy: false, // same reason
              });
              certInjected = true;
              androidTargetId = result.targetId;
            }
    
            // 7. Emit sudo script.
            const scriptPath = writeScript(
              buildSetupScript({
                apIface,
                apAddress: ap_address,
                apSubnet: ap_subnet,
                egressIface,
                explicitPort: explicitPortUsed,
                transparentPort: transparentPortUsed,
                blockQuic: block_quic,
              }),
              "mobile-setup",
            );
    
            return {
              content: [{
                type: "text",
                text: JSON.stringify({
                  status: "success",
                  ap_iface: apIface,
                  ap_iface_reason: ifaceReason,
                  ap_address,
                  ap_subnet,
                  egress_iface: egressIface,
                  explicit_port: explicitPortUsed,
                  transparent_port: transparentPortUsed,
                  block_quic,
                  upstream_set: upstreamSet,
                  cert_injected: certInjected,
                  android_target_id: androidTargetId,
                  sudo_script: scriptPath,
                  sudo_command: `sudo bash ${scriptPath}`,
                  next_steps: [
                    `Run:  sudo bash ${scriptPath}`,
                    "Connect the target device to the proxy-ap WiFi network (credentials live in the proxy-ap-card firmware).",
                    "Call proxy_list_traffic — transparent HTTPS exchanges appear with source=\"transparent\", HTTP with source=\"explicit\".",
                    "When done, call proxy_mobile_teardown and run the emitted sudo script.",
                  ],
                }),
              }],
            };
          } catch (e) {
            return { content: [{ type: "text", text: JSON.stringify({ status: "error", error: errorToString(e) }) }] };
          }
        },
      );
  • Helper function detectApIface() that auto-detects the USB-NCM interface (cdc_ncm or cdc_ether driver) for the proxy-ap-card.
    /** Auto-detect USB-ethernet-to-proxy-ap-card interface. */
    function detectApIface(): { iface: string; reason: string } | null {
      let netIfaces: string[];
      try {
        netIfaces = readdirSync("/sys/class/net");
      } catch {
        return null;
      }
      // Priority 1: cdc_ncm driver (what the proxy-ap-card firmware presents as)
      for (const name of netIfaces) {
        if (ifaceDriver(name) === "cdc_ncm") {
          return { iface: name, reason: "driver=cdc_ncm (proxy-ap-card default)" };
        }
      }
      // Priority 2: cdc_ether (alternative CDC Ethernet class)
      for (const name of netIfaces) {
        if (ifaceDriver(name) === "cdc_ether") {
          return { iface: name, reason: "driver=cdc_ether" };
        }
      }
      return null;
    }
  • Helper function buildSetupScript() that generates the bash script for iptables/sysctl/nmcli configuration executed via sudo.
    function buildSetupScript(opts: {
      apIface: string;
      apSubnet: string;
      apAddress: string;
      egressIface: string;
      explicitPort: number;
      transparentPort: number;
      blockQuic: boolean;
    }): string {
      return `#!/bin/bash
    # proxy-mcp mobile capture — apply host-side network rules.
    # Safe to re-run: rules are idempotent (-C checks before -A).
    set -euo pipefail
    
    AP_IFACE="${opts.apIface}"
    AP_ADDR="${opts.apAddress}"
    AP_SUBNET="${opts.apSubnet}"
    EGRESS_IFACE="${opts.egressIface}"
    HTTP_PORT="${opts.explicitPort}"
    HTTPS_PORT="${opts.transparentPort}"
    
    echo "AP iface       : \$AP_IFACE"
    echo "AP address     : \$AP_ADDR"
    echo "Egress iface   : \$EGRESS_IFACE"
    echo "proxy-mcp HTTP : :\$HTTP_PORT"
    echo "proxy-mcp HTTPS: :\$HTTPS_PORT (transparent)"
    echo
    
    # Let proxy-mcp manage the iface — unhook NetworkManager if it's attached.
    nmcli device set "\$AP_IFACE" managed no 2>/dev/null || true
    
    # Assign a static address if one isn't set. Skipped if already configured.
    if ! ip -o -4 addr show dev "\$AP_IFACE" | grep -qw "\$AP_ADDR"; then
      ip link set "\$AP_IFACE" up
      ip addr flush dev "\$AP_IFACE"
      ip addr add "\$AP_ADDR" dev "\$AP_IFACE"
    fi
    
    # IP forwarding on.
    sysctl -w net.ipv4.ip_forward=1 >/dev/null
    
    # Fresh dedicated chain so rules are easy to flush.
    iptables -t nat -F PROXY_MCP_PREROUTING 2>/dev/null || true
    iptables -t nat -X PROXY_MCP_PREROUTING 2>/dev/null || true
    iptables -t nat -N PROXY_MCP_PREROUTING
    # HTTP → proxy-mcp explicit listener (handles absolute-URL HTTP requests).
    iptables -t nat -A PROXY_MCP_PREROUTING -p tcp --dport 80  -j REDIRECT --to-ports "\$HTTP_PORT"
    # HTTPS → proxy-mcp transparent listener (SNI-based MITM, no CONNECT needed).
    iptables -t nat -A PROXY_MCP_PREROUTING -p tcp --dport 443 -j REDIRECT --to-ports "\$HTTPS_PORT"
    
    # Hook on the AP iface only.
    iptables -t nat -C PREROUTING -i "\$AP_IFACE" -j PROXY_MCP_PREROUTING 2>/dev/null \\
      || iptables -t nat -A PREROUTING -i "\$AP_IFACE" -j PROXY_MCP_PREROUTING
    
    ${opts.blockQuic
      ? `# Block QUIC on the AP iface so apps fall back to TCP/TLS (capturable).
    iptables -C FORWARD -i "\$AP_IFACE" -p udp --dport 443 -j DROP 2>/dev/null \\
      || iptables -A FORWARD -i "\$AP_IFACE" -p udp --dport 443 -j DROP
    `
      : "# QUIC forwarding left intact (will not be captured by the transparent listener).\n"
    }
    # Masquerade out through the real egress.
    iptables -t nat -C POSTROUTING -s "\$AP_SUBNET" -o "\$EGRESS_IFACE" -j MASQUERADE 2>/dev/null \\
      || iptables -t nat -A POSTROUTING -s "\$AP_SUBNET" -o "\$EGRESS_IFACE" -j MASQUERADE
    
    echo
    echo "Done. Connect the target device to the proxy-ap WiFi — traffic will flow"
    echo "through proxy-mcp on ports \$HTTP_PORT (HTTP) and \$HTTPS_PORT (HTTPS)."
    `;
    }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Describes main actions: starting listeners, injecting CA, emitting script. No annotations exist, so description bears full burden. However, it lacks details on side effects (e.g., iptables reset), permission requirements (sudo), or what happens to existing network config. Adequate but not thorough.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Single, well-structured sentence that front-loads the core action ('one-command mobile capture') and lists sub-actions concisely. No superfluous words; every phrase carries meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite 10 parameters and no output schema or annotations, the description does not fully explain the emitted script's details, return values, or error handling. It assumes familiarity with the proxy-ap-card firmware, leaving gaps for less informed agents.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% with detailed parameter descriptions. The tool description adds some context about overall operation but does not significantly enhance understanding of individual parameters beyond what the schema already provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'One-command mobile capture' that starts explicit and transparent listeners, optionally injects a CA on Android, and emits a sudo-runnable script. It specifically pairs with proxy-ap-card firmware, distinguishing it from siblings like proxy_mobile_teardown.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Implied usage is clear for mobile capture with a specific firmware, but no explicit when-to-use or when-not-to-use guidance is given. It does not name alternatives among siblings (e.g., proxy_start_transparent) or mention prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

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