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
9 changes: 4 additions & 5 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
"enabledMcpjsonServers": ["geomcp"]
}
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
36 changes: 19 additions & 17 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -108,24 +108,24 @@ 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)
domain_name (0.6.20240107)
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)
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 @@ -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)
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.3)
rack-session (2.1.1)
base64 (>= 0.1.0)
rack (>= 3.0.0)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
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