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
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://<ip>: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 <serial> 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):
$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):
P=34353
probe() { # prints "host<TAB>name" 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://<that ip>:34353/mcp directly.
d) Zeroconf (optional, faster where available). The app advertises itself as FieldManager-MCP-<manufacturer>_<model>
(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)
-
GET http://<ip>:34353/mcpand read thellm_instructionsfield. It is the app's own, up-to-date description of how to connect: if it differs from this page, follow the app. -
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.
-
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.POST http://<ip>:34353/mcp Content-Type: application/json {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05", "clientInfo":{"name":"<your agent name>","version":"1.0"},"clientToken":"<your code>"}}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.
-
When approved,
resultincludesaccessMode(READ_ONLYorREAD_WRITE) andsessionToken. Keep thesessionTokenand sendAuthorization: Bearer <sessionToken>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. Everyinitializeasks the user again, even with the same code. -
Read
result.instructionsin that same response: the project briefing and the user's saved prompts travel there, and only there (never in the publicGET /mcp).result.projecthas 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 <file>. - More about the app: https://fieldmanagermining.com/ (free 20-day trial).