From 5f9c56941dd359e4488a5dea30706660137d457a Mon Sep 17 00:00:00 2001 From: Anandaroop Roy Date: Wed, 8 Oct 2025 22:33:20 -0400 Subject: [PATCH 1/2] feat: add a nominatim search --- .claude/settings.local.json | 9 ++- .env.example | 3 + .gitignore | 1 + README.md | 14 +++-- app/controllers/mcp_controller.rb | 4 +- app/mcp/tools/nominatim/search.rb | 95 +++++++++++++++++++++++++++++++ lib/nominatim.rb | 45 +++++++++++++++ 7 files changed, 161 insertions(+), 10 deletions(-) create mode 100644 .env.example create mode 100644 app/mcp/tools/nominatim/search.rb create mode 100644 lib/nominatim.rb diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 88a24d1..0ad1c0d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -6,11 +6,10 @@ "mcp__geomcp__geonames_search", "mcp__geomcp__geojson_preview", "mcp__geomcp__wikipedia_search", - "mcp__geomcp__wikipedia_get_page" + "mcp__geomcp__wikipedia_get_page", + "mcp__geomcp__nominatim_search" ] }, "enableAllProjectMcpServers": true, - "enabledMcpjsonServers": [ - "geomcp" - ] -} \ No newline at end of file + "enabledMcpjsonServers": ["geomcp"] +} diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e165d3d --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +PORT=3000 +GEONAMES_USERNAME=REPLACE +NOMINATIM_USER_AGENT=https://github.com/anandaroop/geomcp diff --git a/.gitignore b/.gitignore index c3e206a..f43521f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ # Ignore all environment files. /.env* +!/.env.example # Ignore all logfiles and tempfiles. /log/* diff --git a/README.md b/README.md index 6c81c7a..f6b1247 100644 --- a/README.md +++ b/README.md @@ -18,17 +18,21 @@ It currently exposes the following tools: - Search Wikipedia for articles matching a given query - **wikipedia_get_page** - Get a Wikipedia page by ID using the Wikipedia API +- **nominatim_search** + - Search for toponyms using the OSM' Nominatim API ## Usage with Claude Code -This project contains settings for Claude Code that allow it to make smarter use of the MCP server via subagents. +`geomcp` can be added as an MCP server to Claude Code. + +This project also contains settings that allow Claude Code use the MCP server in a specific way via subagents. ### Subagents - **place-name-researcher** - - Uses the query tools to research place names and return them in a specific CSV format + - Uses the query tools to research place names and return them in a specific CSV format that I have used historically -## Cheat Sheet +### Cheat Sheet ```sh # start the MCP server @@ -54,4 +58,6 @@ $ claude > preview those results ``` -But it is better utilized now via https://github.com/anandaroop/georesearcher +## Usage with `georesearch` + +My preferred way to use this is via a separate custom-built CLI at https://github.com/anandaroop/georesearch. diff --git a/app/controllers/mcp_controller.rb b/app/controllers/mcp_controller.rb index d72ada1..a2a10f8 100644 --- a/app/controllers/mcp_controller.rb +++ b/app/controllers/mcp_controller.rb @@ -13,7 +13,9 @@ def index Tools::GeoNames::Search, Tools::GeoJson::Preview, Tools::Wikipedia::Search, - Tools::Wikipedia::GetPage + Tools::Wikipedia::GetPage, + Tools::Nominatim::Search + ], resources: [ MCP::Resource.new( diff --git a/app/mcp/tools/nominatim/search.rb b/app/mcp/tools/nominatim/search.rb new file mode 100644 index 0000000..29b8bf1 --- /dev/null +++ b/app/mcp/tools/nominatim/search.rb @@ -0,0 +1,95 @@ +class Tools::Nominatim::Search < MCP::Tool + tool_name "nominatim_search" + description "Search for toponyms using the Nominatim API" + input_schema( + properties: { + q: {type: "string", description: "Query terms to search for (may include a feature type, or a parent feature name)"}, + limit: {type: "number", description: "Maximum number of results to return (default 10, maximum 40)", default: 10, maximum: 40}, + namedetails: {type: "boolean", description: "Whether to include additional names for the toponym", default: false}, + countrycodes: {type: "string", description: "Comma-separated list of ISO 3166-1 alpha-2 country codes to restrict the search", default: nil}, + polygon_geojson: {type: "boolean", description: "Whether to return the polygon representation of the feature", default: false}, + feature_type: {type: "string", description: "Restrict results to country, state, city, settlement", default: nil, enum: ["country", "state", "city", "settlement"]} + }, + required: ["q"] + ) + output_schema( + type: "object", + properties: { + type: {type: "string"}, + licence: {type: "string"}, + features: { + type: "array", + items: { + type: "object", + properties: { + type: {type: "string", enum: ["Feature"]}, + properties: { + type: "object", + properties: { + osm_id: {type: "number", description: "OpenStreetMap unique identifier"}, + osm_type: {type: "string", description: "Type of OpenStreetMap element", enum: ["node", "way", "relation"]}, + name: {type: "string", description: "Name of the toponym"}, + display_name: {type: "string", description: "Comma-separated reverse-hierarchical string"}, + place_id: {type: "number"}, + place_rank: {type: "number"}, + importance: {type: "number"}, + addresstype: {type: "string"}, + category: {type: "string"}, + type: {type: "string"} + }, + required: ["osm_type", "osm_id", "name"] + }, + bbox: { + type: "array", items: {type: "number"}, description: "Array of bounding box coordinates [south, north, west, east]" + }, + geometry: { + type: "object", + properties: { + type: {type: "string", description: "GeoJSON geometry type", enum: ["Point", "LineString", "Polygon", "MultiPoint", "MultiLineString", "MultiPolygon"]}, + coordinates: { + type: "array", + items: {any_of: [{type: "number"}, {type: "array", items: {type: "number"}}]}, + description: "Array of coordinates [longitude, latitude] for a Point, or array of such arrays for other geometry types" + } + }, + required: ["type", "coordinates"] + } + }, + required: ["type", "properties", "geometry"] + } + } + }, + required: ["type", "features"] + ) + + class << self + def call( + q:, + limit: nil, + namedetails: false, + countrycodes: nil, + polygon_geojson: false, + feature_type: nil, + server_context: nil + ) + result = Nominatim.search( + q, + limit: limit, + namedetails: namedetails, + countrycodes: countrycodes, + polygon_geojson: polygon_geojson, + feature_type: feature_type + ) + + output_schema.validate_result(result) + + MCP::Tool::Response.new( + [{ + type: "text", + text: result.to_json + }], + structured_content: result + ) + end + end +end diff --git a/lib/nominatim.rb b/lib/nominatim.rb new file mode 100644 index 0000000..af818cc --- /dev/null +++ b/lib/nominatim.rb @@ -0,0 +1,45 @@ +class Nominatim + include HTTParty + + def self.search( + query, + limit: nil, + namedetails: false, + countrycodes: nil, + polygon_geojson: false, + feature_type: nil + ) + params = { + q: query, + format: "geojson" + } + params[:limit] = limit if limit + params[:namedetails] = 1 if namedetails + params[:countrycodes] = countrycodes if countrycodes + params[:polygon_geojson] = 1 if polygon_geojson + params[:featuretype] = feature_type if feature_type + + options = { + query: params, + headers: {"User-Agent" => ENV["NOMINATIM_USER_AGENT"]} + } + Rails.logger.info("Nominatim API request: #{params}") + response = get("https://nominatim.openstreetmap.org/search", options) + pp response + response.to_h + end + + # def self.lookup(osm_id) + # params = { + # osm_ids: "R" + osm_id.to_s, # R for relation may not always be right + # format: "geojson" + # } + # options = { + # query: params, + # headers: {"User-Agent" => ENV["NOMINATIM_USER_AGENT"]} + # } + # Rails.logger.info("Nominatim API request: #{params}") + # response = get("https://nominatim.openstreetmap.org/lookup", options) + # response.to_h + # end +end From f0fca5f86ab482babb34571ced3788738acd0fb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 21:27:43 +0000 Subject: [PATCH 2/2] build(deps): bump solid_queue from 1.2.1 to 1.2.4 Bumps [solid_queue](https://github.com/rails/solid_queue) from 1.2.1 to 1.2.4. - [Release notes](https://github.com/rails/solid_queue/releases) - [Commits](https://github.com/rails/solid_queue/compare/v1.2.1...v1.2.4) --- updated-dependencies: - dependency-name: solid_queue dependency-version: 1.2.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index e4976f2..96bd19b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -86,8 +86,8 @@ GEM ast (2.4.3) base64 (0.3.0) bcrypt_pbkdf (1.1.1) - benchmark (0.4.1) - bigdecimal (3.2.3) + benchmark (0.5.0) + bigdecimal (3.3.1) bindex (0.8.1) bootsnap (1.18.6) msgpack (~> 1.2) @@ -108,7 +108,7 @@ GEM connection_pool (2.5.4) crass (1.0.6) csv (3.3.5) - date (3.4.1) + date (3.5.0) debug (1.11.0) irb (~> 1.10) reline (>= 0.3.8) @@ -116,16 +116,16 @@ GEM dotenv (3.1.8) drb (2.2.3) ed25519 (1.4.0) - erb (5.0.2) + erb (5.1.3) erubi (1.13.1) - et-orbi (1.3.0) + et-orbi (1.4.0) tzinfo - fugit (1.11.2) - et-orbi (~> 1, >= 1.2.11) + fugit (1.12.1) + et-orbi (~> 1.4) raabro (~> 1.4) geo_names (1.0.2) rest-client (~> 2) - globalid (1.2.1) + globalid (1.3.0) activesupport (>= 6.1) http-accept (1.7.0) http-cookie (1.0.8) @@ -183,7 +183,7 @@ GEM mime-types-data (~> 3.2025, >= 3.2025.0507) mime-types-data (3.2025.0916) mini_mime (1.1.5) - minitest (5.25.5) + minitest (5.26.0) msgpack (1.8.0) multi_xml (0.7.2) bigdecimal (~> 3.1) @@ -222,7 +222,7 @@ GEM parser (3.3.9.0) ast (~> 2.4.1) racc - pp (0.6.2) + pp (0.6.3) prettyprint prettyprint (0.2.0) prism (1.5.1) @@ -241,7 +241,7 @@ GEM nio4r (~> 2.0) raabro (1.4.0) racc (1.8.1) - rack (3.2.1) + rack (3.2.3) rack-session (2.1.1) base64 (>= 0.1.0) rack (>= 3.0.0) @@ -279,10 +279,11 @@ GEM thor (~> 1.0, >= 1.2.2) zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.3.0) - rdoc (6.14.2) + rake (13.3.1) + rdoc (6.15.0) erb psych (>= 4.0.0) + tsort regexp_parser (2.11.3) reline (0.6.2) io-console (~> 0.5) @@ -338,11 +339,11 @@ GEM activejob (>= 7.2) activerecord (>= 7.2) railties (>= 7.2) - solid_queue (1.2.1) + solid_queue (1.2.4) activejob (>= 7.1) activerecord (>= 7.1) concurrent-ruby (>= 1.3.1) - fugit (~> 1.11.0) + fugit (~> 1.11) railties (>= 7.1) thor (>= 1.3.1) sqlite3 (2.7.4-aarch64-linux-gnu) @@ -379,7 +380,8 @@ GEM thruster (0.1.15-aarch64-linux) thruster (0.1.15-arm64-darwin) thruster (0.1.15-x86_64-linux) - timeout (0.4.3) + timeout (0.4.4) + tsort (0.2.0) turbo-rails (2.0.16) actionpack (>= 7.1.0) railties (>= 7.1.0) @@ -388,7 +390,7 @@ GEM unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.1.0) - uri (1.0.3) + uri (1.0.4) useragent (0.16.11) web-console (4.2.1) actionview (>= 6.0.0)