# Connect an AI agent to Field Manager on your local network *Last updated: 2026-11-03* Field Manager is the Android GIS app geologists use in the field. If you have it open on a phone or tablet, an AI agent that can run commands (Claude Code, Codex, Gemini CLI, or an agent running inside Termux) can **find the device on your Wi-Fi and connect to it**, so you can work on your real project by talking to the agent. ## For you: copy this prompt to your agent ```text Find my Field Manager device on the local network and connect to it. Follow the instructions at https://fieldmanagermining.com/mcp.md ``` Before you start: - Open **Field Manager on the phone or tablet, with a project open**, and keep the app on screen. The connection only works while a project is open and the app is in the foreground. - The phone and the computer must be on the **same Wi-Fi network** (not a guest network, and no "client isolation" on the router). If the agent runs in **Termux on the same phone**, no network is needed. - When the agent connects, **a dialog appears on the phone with a short code**. Check that it matches the code the agent showed you, then choose **Read-only** (the agent can look but not change anything) or **Read and write**. --- ## Instructions for the AI agent You are helping a user connect to **their own** Field Manager device. Do only what is described here, only when the user asked for it. ### Rules - Scan **only the user's own local private network** (192.168.x.x, 10.x.x.x, 172.16-31.x.x), **only port 34353**, and nothing else. Never scan a mobile-data network, a VPN or a public address. - Prefer the methods in the order below and stop at the first device you find, unless the user wants all devices. - Never ask the user to run technical commands you can run yourself. Talk in plain words. ### Step 1 - Find the device A Field Manager device runs a small server on **port 34353**. A device is a Field Manager device if `GET http://:34353/mcp` answers with JSON that contains an `llm_instructions` field. Try these in order: **a) Same device.** If you run on the phone itself (for example in Termux), use `http://127.0.0.1:34353/mcp`. **b) USB or emulator.** If `adb devices` lists the device, run `adb forward tcp:34353 tcp:34353` (add `-s ` if there is more than one device) and use `http://127.0.0.1:34353/mcp`. **c) Local network sweep.** Run the script for your operating system. Both were tested (Windows PowerShell 5.1; Termux on Android 13). The macOS/Linux variant uses the same commands: **check its output and adapt it if it cannot find the machine's own address**. *Windows (PowerShell):* ```powershell $port = 34353 function Test-Fm($addr) { try { $r = Invoke-RestMethod -Uri "http://${addr}:$port/mcp" -TimeoutSec 3 -ErrorAction Stop if ($r.llm_instructions) { return [pscustomobject]@{ Host = $addr; Name = $r.name } } } catch { } return $null } $found = @() $cl = New-Object Net.Sockets.TcpClient try { if ($cl.ConnectAsync('127.0.0.1', $port).Wait(300)) { $d = Test-Fm '127.0.0.1'; if ($d) { $found += $d } } } catch { } $cl.Close() # Only interfaces that are Up and have an IPv4 default gateway (skips WSL/Hyper-V virtual adapters) $nets = @() foreach ($n in [Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces()) { if ($n.OperationalStatus -ne 'Up' -or $n.NetworkInterfaceType -eq 'Loopback') { continue } $p = $n.GetIPProperties() $gw = $p.GatewayAddresses | Where-Object { $_.Address.AddressFamily -eq 'InterNetwork' -and $_.Address.ToString() -ne '0.0.0.0' } if (-not $gw) { continue } foreach ($u in $p.UnicastAddresses) { if ($u.Address.AddressFamily -eq 'InterNetwork' -and $u.Address.ToString() -match '^(10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)') { $nets += [pscustomobject]@{ Ip = $u.Address.ToString(); Prefix = $u.PrefixLength } } } } foreach ($net in $nets) { $o = $net.Ip -split '\.'; $base = "$($o[0]).$($o[1]).$($o[2])"; $lo = 1; $hi = 254 if ($net.Prefix -gt 24) { # smaller than a /24: only its own range. Larger: still only the /24 around our address $size = [math]::Pow(2, 32 - $net.Prefix) $lo = [int]([math]::Floor([int]$o[3] / $size) * $size) + 1; $hi = $lo + [int]$size - 3 } $clients = @{}; $tasks = New-Object 'System.Collections.Generic.List[System.Threading.Tasks.Task]' foreach ($i in $lo..$hi) { $t = "$base.$i"; if ($t -eq $net.Ip) { continue } $c = New-Object Net.Sockets.TcpClient; $clients[$t] = $c; $tasks.Add($c.ConnectAsync($t, $port)) } try { [void][Threading.Tasks.Task]::WaitAll($tasks.ToArray(), 3000) } catch { } # a sleeping phone may need a moment to answer foreach ($t in @($clients.Keys)) { if ($clients[$t].Connected) { $d = Test-Fm $t; if ($d) { $found += $d } } } foreach ($c in $clients.Values) { $c.Dispose() } } if ($found.Count -eq 0) { 'No Field Manager devices found.' } else { $found | Format-Table -AutoSize } ``` *macOS, Linux and Termux (bash + curl):* ```bash P=34353 probe() { # prints "hostname" if $1 is a Field Manager device local out name out=$(curl -s --connect-timeout 2 -m 4 "http://$1:$P/mcp" 2>/dev/null) || return 1 case "$out" in *llm_instructions*) ;; *) return 1 ;; esac name=$(printf '%s' "$out" | grep -o '"name": *"[^"]*"' | head -1 | cut -d'"' -f4) printf '%s\t%s\n' "$1" "$name" } export -f probe; export P results=$(mktemp) probe 127.0.0.1 >> "$results" # Own PRIVATE addresses of real LAN interfaces: never mobile data (rmnet, ccmni...), VPN or virtual bridges SKIP='^(lo|rmnet|ccmni|pdp|ppp|tun|tap|wwan|docker|br-|veth|utun|awdl|llw|bridge|vmnet|vboxnet|gif|stf|ap)' PRIV='^(10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)' ips="" if command -v ip >/dev/null 2>&1; then ips=$(ip -4 -o addr show scope global 2>/dev/null | awk '{split($4,a,"/"); print $2, a[1]}' | grep -v -E "$SKIP" | awk '{print $2}' | grep -E "$PRIV") fi if [ -z "$ips" ] && command -v ifconfig >/dev/null 2>&1; then ips=$(ifconfig 2>/dev/null | awk '/^[^ \t]/{n=$1; sub(/:$/,"",n)} /inet /{a=$2; sub("addr:","",a); print n, a}' | grep -v -E "$SKIP" | awk '{print $2}' | grep -E "$PRIV") fi if [ -z "$ips" ] && command -v termux-wifi-connectioninfo >/dev/null 2>&1; then # Termux:API app; can take ~10 s ips=$(timeout 15 termux-wifi-connectioninfo 2>/dev/null | grep '"ip"' | cut -d'"' -f4 | grep -E "$PRIV") fi if [ -z "$ips" ]; then # a UDP connect() sends no packet; it only reveals the address of the default route for py in python3 python; do command -v $py >/dev/null 2>&1 || continue ips=$($py -c 'import socket;s=socket.socket(2,2);s.connect(("192.168.0.1",9));print(s.getsockname()[0])' 2>/dev/null | grep -E "$PRIV") [ -n "$ips" ] && break done fi echo "own addresses: ${ips:-none found}" >&2 for base in $(printf '%s\n' $ips | sed 's/\.[0-9]*$//' | sort -u); do export base seq 1 254 | xargs -P 64 -I{} bash -c 'probe "$base.{}"' >> "$results" done if [ -s "$results" ]; then printf 'HOST\tNAME\n'; sort -u "$results"; else echo "No Field Manager devices found."; fi rm -f "$results" ``` If the script prints `own addresses: none found`, ask the user for the IP address of the device (Field Manager shows it in a notice when the server starts) and try `http://:34353/mcp` directly. **d) Zeroconf (optional, faster where available).** The app advertises itself as `FieldManager-MCP-_` (for example `FieldManager-MCP-samsung_SM-A325M`), type `_mcp._tcp`, port 34353. It never includes the project name. On macOS: `dns-sd -B _mcp._tcp`. On Linux: `avahi-browse -rt _mcp._tcp`. Windows and Termux have no built-in tool for this: use the sweep. **Telling devices apart.** Before the user approves anything, `GET /mcp` returns `name`, `version` (the real app version; older app versions report a fixed `1.0`), `device` (manufacturer and model, for example `samsung SM-A325M`) and, only if the owner gave the device a label in their Field Manager account, `device_label`. `device_label` and the real `version` exist only in recent app versions. **The project name is not available before approval.** If you find several devices, show the user the IP, `device` and `device_label` of each and **ask which one is theirs**. If two look identical, the IP is the only difference: the user can read it in the device's Wi-Fi settings. If none is found, see "If it does not work" below. ### Step 2 - Connect (the handshake) 1. `GET http://:34353/mcp` and read the `llm_instructions` field. It is the app's own, up-to-date description of how to connect: **if it differs from this page, follow the app**. 2. Pick a **short code at random** (two words or a word and four digits, different every time; do not reuse an example) and **show it to the user before connecting**: *"I'm connecting to your Field Manager. Code: . A dialog will appear on your device: check that it shows this code, then choose Read-only (I can look but not change anything) or Read and write."* The code only lets the user check that the dialog is yours. It is **not** a credential. 3. Send the request, using the code as `clientToken`. **Use an HTTP timeout of at least 70 seconds**: the request waits while the user decides, up to 60 s. ```text POST http://:34353/mcp Content-Type: application/json {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05", "clientInfo":{"name":"","version":"1.0"},"clientToken":""}} ``` The user chooses the access level and **you cannot raise it**. Ask for Read and write only if the task changes their data, and explain why. 4. When approved, `result` includes `accessMode` (`READ_ONLY` or `READ_WRITE`) and **`sessionToken`**. Keep the `sessionToken` and send `Authorization: Bearer ` in every later request. **Never use the code as Bearer**: it is rejected. This also holds for older app versions that return the field. The token is only in this one response: if you lose it, start the handshake again. Every `initialize` asks the user again, even with the same code. 5. Read `result.instructions` in that same response: the project briefing and the user's saved prompts travel there, and only there (never in the public `GET /mcp`). `result.project` has the project name. Several agents can be connected at the same time, each with its own code and its own `sessionToken`. What the errors mean (tell the user in plain words): | Code | Meaning | What to do | |---|---|---| | `-32001` | The token is not valid, or the session is over: it expires after **4 hours without use**, or when the user opens another project or restarts the app | Use the `sessionToken` from `initialize` (not the code), or repeat the handshake. The user will be asked again | | `-32002` | The user did not respond within **60 seconds** | Reassure them and try again | | `-32003` | The user **rejected** the connection | Do not insist; ask what they want | | `-32004` | The request has no `clientToken` | Pick a code and send it | | `-32006` | Another connection request is already waiting for the user on that device | Wait a moment and try again | ### Step 3 - Everything else comes from the app Once connected, **do not answer technical questions from memory**: the app serves its own, always up-to-date documentation. Call the tool `get_guide` (no arguments returns the index; `topic` returns a document; `topic` plus `section` returns one part). Read the relevant guide before using a capability for the first time. Most users are field geologists with little technical background: talk in geology terms (their drillhole layer, not "the table"), say briefly what you will do, do it, and report the result. ### If it does not work - **Nothing found:** is Field Manager open on the device **with a project open and the app on screen**? Are the device and the computer on the same Wi-Fi? Guest networks and routers with "client isolation" block device-to-device traffic. An active VPN on the computer can also hide the local network. - **Found, but the connection times out:** the phone may have gone to sleep. Wake it, keep Field Manager on screen, retry. - **Windows:** if scripts are blocked, run `powershell -NoProfile -ExecutionPolicy Bypass -File `. - More about the app: https://fieldmanagermining.com/ (free 20-day trial).