
geonames-mcp-server
io.github.cyanheadsv0.1.1Updated Oct 1, 2026
Search GeoNames places, walk admin hierarchies, reverse geocode, get postal codes and country info.
Installation
In SourceWeft
- Open geonames-mcp-server in the dashboard and add it to a workspace.
- Enable the server for the chats that should use its tools.
Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.
Other MCP clients
Follow the launch instructions in the repository.
README
@cyanheads/geonames-mcp-server
Search GeoNames places, walk admin hierarchies, reverse geocode, get postal codes and country info via MCP. STDIO or Streamable HTTP.
Overview
The GeoNames gazetteer: 13M+ places worldwide, each keyed by a stable integer geonameId and linked into an administrative tree from continent to neighborhood. Search places by name and filters, read a place's full record, climb or descend its admin hierarchy, reverse geocode a coordinate, look up postal codes, and read country facts. Runs as a stdio process or a local Streamable HTTP server.
Tools
Capability reference
geonames_search_places tool
query(up to 200 characters) compared permatch:name_required(default),any_field,exact_name, orname_prefix; filterscountries(up to 10),featureClasses,featureCodes(up to 20),cities(cities1000/cities5000/cities15000), andboundingBox. A call needsqueryor at least one ofcountries,featureClasses,featureCodes,boundingBoxlimit1–100 (default 10),offset0–5000,orderByrelevanceorpopulation; reportstotalCountand theeffectiveQuerysent, andnextOffsetnames the next page- Fails before any request with
query_or_filter_required,query_required,unknown_feature_code, orinvalid_bounding_box
geonames_get_place tool
- One
geonameId; an unknown id returnsfound: falsewithguidance adminLevels1–5 with each level's code, name, andgeonameId; timezone (UTC offsets on 1 January and 1 July), bounding box, recorded and DEM elevation, population, Wikipedia URL,alternateNames,postalCodes,links, andidentifiers(IATA, ICAO, FAA, Transport Canada, UN/LOCODE, Wikidata)nameLanguages(up to 20 tags;zhalso matcheszh-CNandzh-TW) filters the alternate names only
geonames_get_hierarchy tool
- One
geonameId;chainruns from Earth and its continent through the country and admin divisions down to the feature, skipping levels it does not sit under - Each level carries
geonameId, feature class and code, country and first-level codes, coordinates, and population; an unknown id returnsfound: false
geonames_get_children tool
hierarchy:administrative(default),tourism,geography, ordependency; only admin divisions and populated places appear- The child list (up to 1,000 per parent) is fetched once and cached, so
nameContains,limit(1–500, default 100), andoffsetcost nothing more; a notice says when GeoNames lists more than 1,000 - An unknown id returns
found: false; a leaf returns an emptychildrenlist with a notice
geonames_reverse_geocode tool
lat/lngresolve tocountryandadminLevels(down to ADM5, each with itsgeonameIdand ISO 3166-2 subdivision code where one exists) or, offshore, theoceannearbylists the nearest populated places (nearbyLimit0–50, default 5;radiusKmup to 300, default 20; optionalcitiestier) or, whenfeatureClasses/featureCodesis set, the nearest features of that type;nearbyKindsays which.citieswith a feature filter fails asconflicting_filtersincludeTimezoneadds the IANA id, UTC offsets, local time, sunrise, and sunset (offsets only offshore)
geonames_find_postal_codes tool
mode:code(needspostalCode),place_name(needsplaceName), ornearby(needslatandlng;radiusKmup to 30, default 10); a missing field, orcountriesinnearby, fails asmode_fields_mismatchcountriesfilter forcodeandplace_name;limit1–100 (default 10). GeoNames reports no total, so a full page is marked truncated- Covers 122 countries; Ireland returns only Eircode routing keys and Malta only letter prefixes
geonames_get_countries tool
- Up to 50
countriesby ISO alpha-2, alpha-3, or numeric code, acontinent,nameContains, or no filter for all 250;limit1–250 (default 50) withoffset - Each row carries ISO and FIPS codes, the country's
geonameId(the starting point forgeonames_get_children), capital, population, area, continent, languages, currency, postal-code format, and mainland bounding box; unknown codes land innotFound
geonames_list_reference tool
topic:feature_classes(9),feature_codes(684, filterable byfeatureClass), orpostal_countries(122, with each country's code range and count);featureClasswith another topic fails asfilter_not_applicablenameContains,limit1–700 (default 100), andoffset; feature classes and codes are bundled and spend no credits
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
GeoNames-specific:
- Data from GeoNames, licensed CC BY 4.0: results you pass on must credit GeoNames. This server is independent of GeoNames.
- Calls the GeoNames JSON web services at
secure.geonames.orgthrough a fixed parameter allowlist per endpoint, since GeoNames silently ignores a parameter it does not know - Per-account pacing at 1,000 requests an hour, with a cooldown after a GeoNames quota error that holds only that account
- A response cache shared across callers and keyed without the account: searches for 1 hour, timezones never, every other lookup for 24 hours. A cache hit spends no credit
- Forgiving inputs: comma-separated lists, any-case enums and codes,
UKforGB, aP.PPLCclass prefix, and a geonames.org URL in place of ageonameId
Agent-friendly output:
- Typed failure reasons that separate the caller's account (
caller_account_rejected) from the operator's (server_account_rejected), a spent quota (quota_exhausted, withdata.windowofhour,day,week, orlocal), and a value GeoNames rejected (upstream_rejected_parameter) - Unknown ids return
found: falsewithguidancerather than an error, and empty or partial pages carry anoticenaming the nextoffsetor the filter to loosen - Placeholders GeoNames uses for "none" (
population: 0, empty admin names,geonameId: 0) are dropped rather than reported as facts - GeoNames-authored text (place, alternate, and admin names) is rendered as inline data in
content[]and kept verbatim instructuredContent
Known limitations:
- Shared quota on a shared deployment. All callers without their own username share the server account's 1,000 credits an hour. A burst of reverse geocodes (up to 7 credits each) can exhaust it, and GeoNames does not say when the hourly window resets.
- Search pages no further than offset 5000 on the free tier, and
limitis capped at 100, so a result set is reachable up to its 5,100th row. - Children are capped at 1,000 per parent. A notice says when GeoNames lists more than it returns.
- Postal data covers 122 countries. Ireland and Malta return only code prefixes. US nearby lookups place the first row at the query point rather than the ZIP centroid. GeoNames reports no total for postal searches, so truncation is inferred from a full page.
- Coastal points can resolve to the ocean. The subdivision lookup uses no coastal buffer, so a point just offshore returns the sea while its nearby places are on land.
- Bounding boxes cannot cross the 180° meridian. Split such an area into two searches.
- Nearby radius tops out at 300 km for places and features and 30 km for postal codes, GeoNames' free-tier ceilings.
- Microstates and enclaves can resolve to the surrounding country. The subdivision polygons do not always carve them out: a point inside Vatican City returns Italy.
- Nearest populated places include sections and historical places. In a dense city the nearest rows are often
PPLXquarters orPPLHformer districts; each row'sfeatureCodesays which, andcitiesrestricts to places above a population tier. - Offshore timezones are offsets only. No IANA id, local time, sunrise, or sunset is available at sea.
exact_namematches alternate and historical names, so a result'snamecan differ from the query: "Springfield" can return Plattsburg or Palmyra, MO.- Data is community-edited and provided "as is". Many features have no recorded population (reported as unavailable) or elevation.
Getting started
Add the following to your MCP client configuration file, with your GeoNames username in place of the placeholder.
Or with npx (no Bun required):
Or with Docker:
For Streamable HTTP, set the transport and start the server:
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+).
- A free GeoNames account with free web services enabled on its account page. GeoNames rejects calls from an account until they are enabled.
Installation
- Clone the repository:
- Navigate into the directory:
- Install dependencies:
- Configure environment:
Configuration
Every GeoNames call spends credits from a GeoNames account. GEONAMES_USERNAME is the server's account: a free GeoNames account with free web services enabled on its account page. Every tool also takes geonamesUsername (alias username), so a caller on a shared deployment can spend their own account instead of the server's. When neither is set, calls fail with username_required; only the bundled feature_classes and feature_codes topics of geonames_list_reference work without an account. The account name never appears in output, error text, logs, or cache keys.
A free account gets 1,000 credits an hour and 10,000 a day. Static lookups are cached, so a repeat call spends nothing.
See .env.example for the full list of optional overrides.
Running the server
Local development
-
Build and run the production version:
-
Run checks and tests:
Project structure
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor logging,ctx.statefor storage - Register new tools in
src/mcp-server/tools/definitions/index.ts - Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
License
This project is licensed under the Apache 2.0 License. See the LICENSE file for details.
Source: README.md at commit e82d6ed
Tools
0Version history
1- v0.1.1LatestOct 1, 2026

