From 4187b752a9c6ff60a49981de142cfa5014267ecd 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 --- .env.example | 3 + .gitignore | 1 + app/controllers/mcp_controller.rb | 4 +- app/mcp/tools/nominatim/search.rb | 95 +++++++++++++++++++++++++++++++ lib/nominatim.rb | 45 +++++++++++++++ 5 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 .env.example create mode 100644 app/mcp/tools/nominatim/search.rb create mode 100644 lib/nominatim.rb 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/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 17a3cb5cb8f5d71ebf17667b5a38e392ac3d9aa7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 02:46:30 +0000 Subject: [PATCH 2/2] build(deps): bump httparty from 0.23.1 to 0.23.2 Bumps [httparty](https://github.com/jnunemaker/httparty) from 0.23.1 to 0.23.2. - [Release notes](https://github.com/jnunemaker/httparty/releases) - [Changelog](https://github.com/jnunemaker/httparty/blob/main/Changelog.md) - [Commits](https://github.com/jnunemaker/httparty/compare/v0.23.1...v0.23.2) --- updated-dependencies: - dependency-name: httparty dependency-version: 0.23.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index e4976f2..14e7451 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -87,7 +87,7 @@ GEM base64 (0.3.0) bcrypt_pbkdf (1.1.1) benchmark (0.4.1) - bigdecimal (3.2.3) + bigdecimal (3.3.0) bindex (0.8.1) bootsnap (1.18.6) msgpack (~> 1.2) @@ -130,7 +130,7 @@ GEM http-accept (1.7.0) http-cookie (1.0.8) domain_name (~> 0.5) - httparty (0.23.1) + httparty (0.23.2) csv mini_mime (>= 1.0.0) multi_xml (>= 0.5.2)