Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
PORT=3000
GEONAMES_USERNAME=REPLACE
NOMINATIM_USER_AGENT=https://github.com/anandaroop/geomcp
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

# Ignore all environment files.
/.env*
!/.env.example

# Ignore all logfiles and tempfiles.
/log/*
Expand Down
10 changes: 5 additions & 5 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -226,7 +226,7 @@ GEM
prettyprint
prettyprint (0.2.0)
prism (1.5.1)
propshaft (1.2.1)
propshaft (1.3.1)
actionpack (>= 7.0.0)
activesupport (>= 7.0.0)
rack
Expand All @@ -241,7 +241,7 @@ GEM
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.8.1)
rack (3.2.1)
rack (3.2.2)
rack-session (2.1.1)
base64 (>= 0.1.0)
rack (>= 3.0.0)
Expand Down Expand Up @@ -388,7 +388,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)
Expand Down
4 changes: 3 additions & 1 deletion app/controllers/mcp_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
95 changes: 95 additions & 0 deletions app/mcp/tools/nominatim/search.rb
Original file line number Diff line number Diff line change
@@ -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
45 changes: 45 additions & 0 deletions lib/nominatim.rb
Original file line number Diff line number Diff line change
@@ -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