openclaw/docs/plugins/geolocation.md
Peter Steinberger fe609d4ce1
feat: let agents query online people and device activity (#159117)
* feat(presence): let agents inspect people and active devices

Expose a read-only presence tool for online people, requester-scoped device activity, and optional network and IP geography. Reuse live connection, profile, node, and geolocation owners; preserve activity provenance and worker requester authority without retaining activity history.

* fix(presence): register tool display metadata

* fix(presence): require live source authority for agent reads

Preserve authenticated Team read scopes and explicitly admitted owner or scheduled sources before synthetic Gateway dispatch. Retain the same source through worker execution and revalidate before returning observations. Append the advertised RPC to preserve existing method indices.

Include the already-landed c0e6951d6f Cron fixture scheduler corrections so the branch typechecks independently. Add real local router and worker admission regressions for unknown, limited, scheduled, and revoked sources.

* fix(protocol): regenerate appended presence method enum

* fix(tooling): include presence schema in PR wrapper extraction

* test(gateway): drain buffered MCP keepalives on shutdown

* test: qualify synthetic approval kind assertion

Keep the negative-type assertion from #159132 explicit in the inventory and bind its allowance to the exact synthetic invalid-input fixture. The existing zero-any and inventory checks remain active, and a changed-input control fails at the fixture qualification.

* test(presence): reconcile rebased lookup and inventory fixtures

* build(presence): refresh plugin assets after dependency update

* test(presence): verify activity reporting retains device attribution
2026-09-27 11:38:53 +00:00

9.5 KiB

summary read_when title
Resolve a connecting client's IP address to a coarse city using a locally cached database, with no per-lookup third-party calls
You want to see where the people using your Gateway are connecting from
You are choosing or replacing the IP-geolocation database and need its license terms
A location is missing, wrong, or stuck and you need to know which layer failed
Geolocation plugin

The bundled geolocation plugin turns a connecting client's IP address into a coarse city. It ships with OpenClaw, downloads its database on first use, and answers entirely from that local copy, so a lookup never sends an address to a third party.

It owns exactly one thing: address to place. It does not decide which addresses get looked up, does not store results, and is not an authorization input. The Control UI uses it to label the devices on a person's Activity card. The core presence tool uses the same lookup owner when the agent requests include: ["location"].

Quickstart

The plugin is bundled and active by default. To see it work, open Activity, pick a person, and look at their device row. A remote client shows its address and the resolved city:

openclaw-control-ui  MacIntel · 8.8.8.8 · Europe/Vienna  Mountain View, California ⓘ

The first view after a fresh install shows no city while the database downloads; the row fills itself in once it is ready, without a reload. To check the plugin directly:

curl -s "http://127.0.0.1:18789/plugins/geolocation/lookup?ip=8.8.8.8" -H "Authorization: Bearer <GATEWAY_TOKEN>"
{
  "found": true,
  "city": "Mountain View",
  "region": "California",
  "country": "United States",
  "countryCode": "US",
  "attribution": { "text": "IP Geolocation by DB-IP", "url": "https://db-ip.com" }
}

Use an address that is actually routable. Reserved ranges such as 203.0.113.0/24 are absent from the database and answer {"found": false}:

{
  "found": false,
  "attribution": { "text": "IP Geolocation by DB-IP", "url": "https://db-ip.com" }
}

The first call also downloads the database, so expect it to take up to a minute while later calls answer from the local copy.

Gateway lookup

Authenticated callers with operator.read can use geolocation.lookup with {"ips":["8.8.8.8"]}. A request accepts at most 200 IPv4 or IPv6 addresses and returns one entry per distinct address in results. Each entry includes ip, status (found, not-found, or unavailable), the available place fields, and attribution.

The presence tool calls this method only when location is requested. Disabling the plugin or a database outage leaves presence and network details available; location is reported as unavailable. A lookup uses the recorded connection IP, never requests GPS, and does not send the IP to an external service. Client time zones remain separate reported facts.

Why some clients never show a location

A location only appears when the Gateway recorded a usable public address for that client, and often it did not:

  • Connect handling omits ip entirely for loopback clients, so anything reaching the Gateway over an SSH tunnel or local port forward has no address to resolve.
  • Tailscale clients arrive on a 100.64/10 carrier-grade-NAT address and LAN clients on a private one. Both are recorded and displayed, but no geolocation database contains them, so the plugin answers found: false for these ranges without loading the database at all. A tailnet-only or LAN-only Gateway therefore never downloads one.
  • Mobile carriers, VPNs, and corporate egress resolve to the operator's exit point, not the person. The answer is confidently wrong rather than missing.

This is why the device row also carries the client-reported time zone. A browser knows its own zone regardless of how it reached the Gateway, so Europe/Vienna keeps working exactly where the address stops being informative. Treat the city as a hint and the zone as the more reliable signal. See Presence for how both fields are produced.

Configuration

Every option is optional. The defaults are a working setup.

Option Default Purpose
databaseUrl monthly DB-IP City Lite build MMDB source. {yyyy} and {mm} expand to a release month.
attributionText IP Geolocation by DB-IP Credit shown next to every result.
attributionUrl https://db-ip.com Link target for the credit.
refreshDays 30 How stale the cached database may get before it is downloaded again.
{
  plugins: {
    entries: {
      geolocation: {
        config: {
          refreshDays: 7,
        },
      },
    },
  },
}

Monthly builds appear a few days into the month, so the plugin tries the current month and falls back to the previous one. A source that needs no month substitution is fetched as written.

Using a different database

Set databaseUrl together with both attribution fields. The credit belongs to whichever dataset you point at, so changing the source without changing the credit misattributes the data:

{
  plugins: {
    entries: {
      geolocation: {
        config: {
          databaseUrl: "https://example.internal/geoip/city.mmdb",
          attributionText: "IP data by Example",
          attributionUrl: "https://example.internal",
        },
      },
    },
  },
}

Any MaxMind-format city database works, including a self-hosted mirror or a commercial build you already license. The cache file is named after the source URL, so switching sources cannot serve the previous provider's data under the new provider's credit.

Data license

The default database is DB-IP City Lite, licensed CC BY 4.0. That license requires attribution, which is why the credit is part of every response and is rendered next to the value rather than buried in settings.

OpenClaw downloads this database at runtime and never redistributes it, so the license attaches to your deployment's use of the data, not to OpenClaw itself. Plugin code and the maxmind reader it uses are MIT. No free city-level IP database is MIT-licensed; the obligation lives with the data.

Expect city-level accuracy in the 55-80% range, and worse for the mobile, VPN, and CGNAT cases above.

How the database is managed

The download is lazy and demand-driven. It happens on the first authorized lookup of a public address, such as opening a person's Activity view or asking the agent for presence with location details. A Gateway nobody inspects — or one reached only over loopback, a tunnel, a LAN, or a tailnet — never downloads anything.

On that first qualifying lookup the plugin fetches the database into <state-dir>/geolocation/, parses it before publishing it, and keeps it until it ages past refreshDays.

Four behaviors are worth knowing because they decide what you see during a failure:

  • The response is read against a compressed ceiling and inflated against an on-disk ceiling, both enforced while reading. A replaced source cannot allocate an unbounded body, and a compression bomb cannot inflate past the limit.
  • A body that does not parse as an MMDB is discarded without replacing a working database. A rate-limit page or truncated download cannot break a Gateway that was working a minute ago.
  • A failed download or invalid database serves the cached copy and logs a warning. If a valid download cannot be saved to disk, lookups use it from memory and log the cache-write failure.
  • Concurrent first lookups in one Gateway share one download. Separate Gateways stage their downloads independently and close the complete file before replacing the cache, so another Gateway never reads a partially written download.

Troubleshooting

No location on any device row. Either the Gateway recorded no address — a row showing only a platform and time zone has no ip, which is expected for loopback and tunneled clients — or every address present is private or carrier-grade NAT, which the plugin answers without consulting the database. Nothing is broken in either case.

Every lookup returns 503. The database is unavailable — still downloading, or every candidate URL failed. Check the Gateway log for geolocation: downloaded or a geolocation database download failed line naming each URL it tried. A Gateway with no outbound network access cannot fetch the database; point databaseUrl at an internal mirror instead.

A found: false answer. The database has no entry for that address. This is a data limitation, not a failure. Note that 503 and found: false are deliberately different: one means the plugin could not answer, the other means the database has no place for that address.

A wrong city. Confirm the address is the person's, not a VPN or carrier exit. If it is genuinely wrong, DB-IP accepts corrections, or point databaseUrl at a commercial database with better coverage.

  • Presence — how connect records the address and time zone this plugin reads
  • Manage plugins — enabling, disabling, and configuring bundled plugins
  • Trusted proxy auth — how the Gateway determines a client address behind a proxy