Prep for PyPI release: error handling, better docs, MIT license
- Add BLMAPIError exception with user-friendly error messages - Rename is_public to allows_public_access with correct logic (BIA/DOD are federal but restricted, not public access) - Add MIT license - Expand pyproject.toml with URLs, keywords, classifiers - Rewrite README with badges, use cases, coverage map, examples
This commit is contained in:
parent
0ef2c70af4
commit
d8d160efdc
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Ryan Malloy
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
126
README.md
126
README.md
@ -1,65 +1,84 @@
|
||||
# mcblmplss
|
||||
|
||||
FastMCP server for querying BLM (Bureau of Land Management) land data by coordinates.
|
||||
[](https://pypi.org/project/mcblmplss/)
|
||||
[](https://pypi.org/project/mcblmplss/)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
## Features
|
||||
**MCP server for querying U.S. public land data by coordinates.**
|
||||
|
||||
- **PLSS** - Public Land Survey System (Section, Township, Range)
|
||||
- **Surface Management** - Who manages the land (BLM, Forest Service, NPS, Private, etc.)
|
||||
- **Mining Claims** - Active and closed mining claims from MLRS database
|
||||
Drop a pin anywhere in the western U.S. and instantly get:
|
||||
- **PLSS location** — Section 12, Township 4N, Range 6E
|
||||
- **Land manager** — BLM, Forest Service, National Park, Private, etc.
|
||||
- **Mining claims** — Active lode/placer claims with serial numbers
|
||||
|
||||
## When would I use this?
|
||||
|
||||
| Use Case | What you get |
|
||||
|----------|--------------|
|
||||
| **Dispersed camping** | Check if land is BLM/Forest Service before setting up camp |
|
||||
| **Land research** | Get legal descriptions for title searches or due diligence |
|
||||
| **Prospecting** | Find existing mining claims before staking your own |
|
||||
| **Navigation** | Convert GPS coordinates to the township/range system used on paper maps |
|
||||
| **GIS workflows** | Programmatic access to BLM cadastral data |
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Run directly with uvx
|
||||
uvx mcblmplss
|
||||
pip install mcblmplss
|
||||
```
|
||||
|
||||
# Add to Claude Code
|
||||
claude mcp add blm-land "uvx mcblmplss"
|
||||
Or run directly without installing:
|
||||
|
||||
```bash
|
||||
uvx mcblmplss
|
||||
```
|
||||
|
||||
### Add to Claude Code
|
||||
|
||||
```bash
|
||||
claude mcp add blm "uvx mcblmplss"
|
||||
```
|
||||
|
||||
## Tools
|
||||
|
||||
### PLSS (Public Land Survey System)
|
||||
### `get_plss_location`
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `get_plss_location` | Get Section/Township/Range as human-readable text |
|
||||
| `get_plss_details` | Get full PLSS data as structured object |
|
||||
Convert coordinates to Section/Township/Range.
|
||||
|
||||
```
|
||||
> get_plss_location(40.0, -105.0)
|
||||
> get_plss_location(latitude=40.0, longitude=-105.0)
|
||||
|
||||
Section 9, Township 1N, Range 68W, 6th Meridian
|
||||
State: CO
|
||||
PLSS ID: CO060010S0680W0SN090
|
||||
```
|
||||
|
||||
### Surface Management Agency
|
||||
### `get_land_manager`
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `get_land_manager` | Determine who manages the land |
|
||||
| `get_land_manager_details` | Get full SMA data as structured object |
|
||||
Find out who manages the land (and whether you can access it).
|
||||
|
||||
```
|
||||
> get_land_manager(38.5, -110.5)
|
||||
> get_land_manager(latitude=38.5, longitude=-110.5)
|
||||
|
||||
Bureau of Land Management (BLM) - Department of the Interior
|
||||
Unit: Bureau of Land Management
|
||||
State: UT
|
||||
Status: Federal, Public land
|
||||
Status: Federal, Public access
|
||||
```
|
||||
|
||||
### Mining Claims
|
||||
```
|
||||
> get_land_manager(latitude=40.0, longitude=-105.0)
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `get_mining_claims` | Find mining claims at/near location |
|
||||
| `get_mining_claims_details` | Get full claim data as structured objects |
|
||||
Private (PVT)
|
||||
State: CO
|
||||
```
|
||||
|
||||
### `get_mining_claims`
|
||||
|
||||
Find active mining claims at a location.
|
||||
|
||||
```
|
||||
> get_mining_claims(39.5, -117.0)
|
||||
> get_mining_claims(latitude=39.5, longitude=-117.0)
|
||||
|
||||
Found 42 mining claim(s):
|
||||
|
||||
@ -68,31 +87,52 @@ MAGA #6
|
||||
Type: Lode Claim
|
||||
Status: Active
|
||||
Acres: 20.66
|
||||
|
||||
MS 2
|
||||
Serial: NV105223666
|
||||
Type: Lode Claim
|
||||
Status: Active
|
||||
Acres: 20.66
|
||||
...
|
||||
```
|
||||
|
||||
## Coverage
|
||||
|
||||
Data available for **30 western/midwestern states** where federal land surveys were conducted. Not available for eastern seaboard states or Texas (different survey systems).
|
||||
Data is available for **30 states** where the Public Land Survey System was used:
|
||||
|
||||

|
||||
|
||||
**Not covered:** Eastern seaboard states (use metes-and-bounds), Texas (independent surveys), Hawaii.
|
||||
|
||||
## Error Handling
|
||||
|
||||
The server returns clear error messages when:
|
||||
|
||||
- **Outside PLSS coverage**: "No PLSS data found. Location may be outside surveyed areas."
|
||||
- **API timeout**: "BLM API request timed out. The service may be slow or unavailable."
|
||||
- **No mining claims**: "No mining claims found at this location."
|
||||
|
||||
## Data Sources
|
||||
|
||||
All data queried from official BLM ArcGIS REST services:
|
||||
All data comes from official BLM ArcGIS REST services:
|
||||
|
||||
- [BLM National PLSS](https://gis.blm.gov/arcgis/rest/services/Cadastral/BLM_Natl_PLSS_CadNSDI/MapServer)
|
||||
- [Surface Management Agency](https://gis.blm.gov/arcgis/rest/services/lands/BLM_Natl_SMA_LimitedScale/MapServer)
|
||||
- [Mining Claims (MLRS)](https://gis.blm.gov/nlsdb/rest/services/Mining_Claims/MiningClaims/MapServer)
|
||||
| Data | Source | Update Frequency |
|
||||
|------|--------|------------------|
|
||||
| PLSS | [BLM National PLSS CadNSDI](https://gis.blm.gov/arcgis/rest/services/Cadastral/BLM_Natl_PLSS_CadNSDI/MapServer) | Quarterly |
|
||||
| Surface Management | [BLM SMA](https://gis.blm.gov/arcgis/rest/services/lands/BLM_Natl_SMA_LimitedScale/MapServer) | Annual |
|
||||
| Mining Claims | [BLM MLRS](https://gis.blm.gov/nlsdb/rest/services/Mining_Claims/MiningClaims/MapServer) | Weekly |
|
||||
|
||||
## Architecture
|
||||
**Disclaimer:** This data is for informational purposes only. For legal land descriptions, consult official BLM records or a licensed surveyor.
|
||||
|
||||
Uses FastMCP's mixin pattern for composable tool modules:
|
||||
## Development
|
||||
|
||||
```bash
|
||||
git clone https://git.supported.systems/MCP/mcblmplss.git
|
||||
cd mcblmplss
|
||||
uv sync
|
||||
uv run mcblmplss
|
||||
```
|
||||
src/mcblmplss/
|
||||
├── server.py # Main FastMCP server
|
||||
├── client.py # Shared BLM API client
|
||||
└── mixins/
|
||||
├── plss.py # PLSS tools
|
||||
├── surface_management.py # SMA tools
|
||||
└── mining_claims.py # Mining claims tools
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
@ -1,18 +1,33 @@
|
||||
[project]
|
||||
name = "mcblmplss"
|
||||
version = "2024.12.03"
|
||||
description = "FastMCP server for querying BLM Public Land Survey System (PLSS) data by coordinates"
|
||||
description = "MCP server for querying BLM land data: PLSS coordinates, surface management agency, and mining claims"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
requires-python = ">=3.11"
|
||||
authors = [
|
||||
{name = "Ryan Malloy", email = "ryan@supported.systems"}
|
||||
]
|
||||
keywords = ["mcp", "fastmcp", "blm", "plss", "cadastral", "land-survey", "gis"]
|
||||
keywords = [
|
||||
"mcp",
|
||||
"fastmcp",
|
||||
"blm",
|
||||
"plss",
|
||||
"cadastral",
|
||||
"land-survey",
|
||||
"gis",
|
||||
"mining-claims",
|
||||
"surface-management",
|
||||
"land-ownership",
|
||||
"public-lands",
|
||||
]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Topic :: Scientific/Engineering :: GIS",
|
||||
]
|
||||
dependencies = [
|
||||
@ -20,6 +35,11 @@ dependencies = [
|
||||
"httpx>=0.28.1",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://git.supported.systems/MCP/mcblmplss"
|
||||
Repository = "https://git.supported.systems/MCP/mcblmplss"
|
||||
Issues = "https://git.supported.systems/MCP/mcblmplss/issues"
|
||||
|
||||
[project.scripts]
|
||||
mcblmplss = "mcblmplss:main"
|
||||
|
||||
|
||||
@ -4,14 +4,21 @@ Shared HTTP client for BLM ArcGIS REST API queries.
|
||||
Provides common identify/query operations against BLM MapServer endpoints.
|
||||
"""
|
||||
|
||||
import httpx
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class BLMAPIError(Exception):
|
||||
"""Error communicating with BLM ArcGIS services."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BLMClient:
|
||||
"""Async HTTP client for BLM ArcGIS REST services."""
|
||||
|
||||
def __init__(self, timeout: float = 30.0):
|
||||
def __init__(self, timeout: float = 60.0):
|
||||
self.timeout = timeout
|
||||
|
||||
async def identify(
|
||||
@ -36,6 +43,9 @@ class BLMClient:
|
||||
|
||||
Returns:
|
||||
List of result dictionaries with layerId and attributes
|
||||
|
||||
Raises:
|
||||
BLMAPIError: If the API request fails
|
||||
"""
|
||||
params = {
|
||||
"f": "json",
|
||||
@ -44,15 +54,27 @@ class BLMClient:
|
||||
"sr": "4326",
|
||||
"layers": layers,
|
||||
"tolerance": str(tolerance),
|
||||
"mapExtent": f"{longitude-1},{latitude-1},{longitude+1},{latitude+1}",
|
||||
"mapExtent": f"{longitude - 1},{latitude - 1},{longitude + 1},{latitude + 1}",
|
||||
"imageDisplay": "100,100,96",
|
||||
"returnGeometry": "true" if return_geometry else "false",
|
||||
}
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
response = await client.get(f"{base_url}/identify", params=params)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
except httpx.TimeoutException:
|
||||
raise BLMAPIError("BLM API request timed out. The service may be slow or unavailable.")
|
||||
except httpx.HTTPStatusError as e:
|
||||
raise BLMAPIError(f"BLM API returned error {e.response.status_code}")
|
||||
except httpx.RequestError as e:
|
||||
raise BLMAPIError(f"Failed to connect to BLM API: {e}")
|
||||
|
||||
# Check for ArcGIS error response
|
||||
if "error" in data:
|
||||
error_msg = data["error"].get("message", "Unknown error")
|
||||
raise BLMAPIError(f"BLM API error: {error_msg}")
|
||||
|
||||
return data.get("results", [])
|
||||
|
||||
@ -82,6 +104,9 @@ class BLMClient:
|
||||
|
||||
Returns:
|
||||
List of feature attribute dictionaries
|
||||
|
||||
Raises:
|
||||
BLMAPIError: If the API request fails
|
||||
"""
|
||||
params = {
|
||||
"f": "json",
|
||||
@ -98,16 +123,26 @@ class BLMClient:
|
||||
params["spatialRel"] = "esriSpatialRelIntersects"
|
||||
params["inSR"] = "4326"
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
response = await client.get(
|
||||
f"{base_url}/{layer_id}/query", params=params
|
||||
)
|
||||
response = await client.get(f"{base_url}/{layer_id}/query", params=params)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
except httpx.TimeoutException:
|
||||
raise BLMAPIError("BLM API request timed out. The service may be slow or unavailable.")
|
||||
except httpx.HTTPStatusError as e:
|
||||
raise BLMAPIError(f"BLM API returned error {e.response.status_code}")
|
||||
except httpx.RequestError as e:
|
||||
raise BLMAPIError(f"Failed to connect to BLM API: {e}")
|
||||
|
||||
# Check for ArcGIS error response
|
||||
if "error" in data:
|
||||
error_msg = data["error"].get("message", "Unknown error")
|
||||
raise BLMAPIError(f"BLM API error: {error_msg}")
|
||||
|
||||
features = data.get("features", [])
|
||||
return [f.get("attributes", {}) for f in features]
|
||||
|
||||
|
||||
# Shared client instance - longer timeout for slower services like mining claims
|
||||
# Shared client instance
|
||||
blm_client = BLMClient(timeout=60.0)
|
||||
|
||||
@ -4,8 +4,8 @@ MCP Mixins for BLM data services.
|
||||
Each mixin provides tools for a specific BLM data domain.
|
||||
"""
|
||||
|
||||
from mcblmplss.mixins.mining_claims import MiningClaimsMixin
|
||||
from mcblmplss.mixins.plss import PLSSMixin
|
||||
from mcblmplss.mixins.surface_management import SurfaceManagementMixin
|
||||
from mcblmplss.mixins.mining_claims import MiningClaimsMixin
|
||||
|
||||
__all__ = ["PLSSMixin", "SurfaceManagementMixin", "MiningClaimsMixin"]
|
||||
|
||||
@ -4,10 +4,10 @@ Mining Claims mixin for BLM MCP server.
|
||||
Provides tools for querying active and closed mining claims from BLM's MLRS database.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from fastmcp.contrib.mcp_mixin import MCPMixin, mcp_tool
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from mcblmplss.client import blm_client
|
||||
from mcblmplss.client import BLMAPIError, blm_client
|
||||
|
||||
# Mining Claims MapServer (separate server from main BLM arcgis)
|
||||
MINING_URL = "https://gis.blm.gov/nlsdb/rest/services/Mining_Claims/MiningClaims/MapServer"
|
||||
@ -71,7 +71,7 @@ class MiningClaimsMixin(MCPMixin):
|
||||
|
||||
@mcp_tool(
|
||||
name="get_mining_claims",
|
||||
description="Find active mining claims at or near coordinates. Returns claim names, serial numbers, and types.",
|
||||
description="Find active mining claims at or near coordinates.",
|
||||
)
|
||||
async def get_mining_claims(
|
||||
self,
|
||||
@ -125,9 +125,7 @@ class MiningClaimsMixin(MCPMixin):
|
||||
self,
|
||||
latitude: float = Field(description="Latitude in decimal degrees (WGS84)"),
|
||||
longitude: float = Field(description="Longitude in decimal degrees (WGS84)"),
|
||||
include_closed: bool = Field(
|
||||
default=False, description="Include closed/void claims"
|
||||
),
|
||||
include_closed: bool = Field(default=False, description="Include closed/void claims"),
|
||||
tolerance: int = Field(default=10, description="Search radius in pixels"),
|
||||
) -> MiningClaimsResult:
|
||||
"""Get full mining claims data including legal descriptions."""
|
||||
@ -145,9 +143,16 @@ class MiningClaimsMixin(MCPMixin):
|
||||
if include_closed:
|
||||
layers = f"all:{LAYER_ACTIVE},{LAYER_CLOSED}"
|
||||
|
||||
try:
|
||||
results = await blm_client.identify(
|
||||
MINING_URL, latitude, longitude, layers, tolerance=tolerance
|
||||
)
|
||||
except BLMAPIError as e:
|
||||
return MiningClaimsResult(
|
||||
latitude=latitude,
|
||||
longitude=longitude,
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
if not results:
|
||||
return MiningClaimsResult(
|
||||
|
||||
@ -4,10 +4,10 @@ PLSS (Public Land Survey System) mixin for BLM MCP server.
|
||||
Provides tools for querying Section, Township, and Range from coordinates.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from fastmcp.contrib.mcp_mixin import MCPMixin, mcp_tool
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from mcblmplss.client import blm_client
|
||||
from mcblmplss.client import BLMAPIError, blm_client
|
||||
|
||||
# BLM Cadastral MapServer
|
||||
PLSS_URL = "https://gis.blm.gov/arcgis/rest/services/Cadastral/BLM_Natl_PLSS_CadNSDI/MapServer"
|
||||
@ -57,9 +57,9 @@ def _parse_township(attrs: dict) -> PLSSLocation:
|
||||
|
||||
def _parse_section(attrs: dict, township: PLSSLocation | None) -> PLSSLocation:
|
||||
"""Parse section attributes from API response."""
|
||||
sec_num = (
|
||||
attrs.get("First Division Number", "") or attrs.get("FRSTDIVNO", "")
|
||||
).lstrip("0") or "0"
|
||||
sec_num = (attrs.get("First Division Number", "") or attrs.get("FRSTDIVNO", "")).lstrip(
|
||||
"0"
|
||||
) or "0"
|
||||
div_id = attrs.get("First Division Identifier", "") or attrs.get("FRSTDIVID", "")
|
||||
|
||||
if township:
|
||||
@ -90,7 +90,7 @@ class PLSSMixin(MCPMixin):
|
||||
|
||||
@mcp_tool(
|
||||
name="get_plss_location",
|
||||
description="Get Section/Township/Range for coordinates. Returns the PLSS legal land description.",
|
||||
description="Get Section/Township/Range for coordinates (PLSS legal description).",
|
||||
)
|
||||
async def get_plss_location(
|
||||
self,
|
||||
@ -143,9 +143,16 @@ class PLSSMixin(MCPMixin):
|
||||
|
||||
async def _query_plss(self, latitude: float, longitude: float) -> PLSSResult:
|
||||
"""Query BLM PLSS API for location."""
|
||||
try:
|
||||
results = await blm_client.identify(
|
||||
PLSS_URL, latitude, longitude, f"all:{LAYER_TOWNSHIP},{LAYER_SECTION}"
|
||||
)
|
||||
except BLMAPIError as e:
|
||||
return PLSSResult(
|
||||
latitude=latitude,
|
||||
longitude=longitude,
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
if not results:
|
||||
return PLSSResult(
|
||||
|
||||
@ -4,10 +4,10 @@ Surface Management Agency mixin for BLM MCP server.
|
||||
Provides tools for determining who manages federal lands (BLM, USFS, NPS, etc.).
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from fastmcp.contrib.mcp_mixin import MCPMixin, mcp_tool
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from mcblmplss.client import blm_client
|
||||
from mcblmplss.client import BLMAPIError, blm_client
|
||||
|
||||
# Surface Management Agency MapServer
|
||||
SMA_URL = "https://gis.blm.gov/arcgis/rest/services/lands/BLM_Natl_SMA_LimitedScale/MapServer"
|
||||
@ -52,7 +52,9 @@ class LandManager(BaseModel):
|
||||
admin_unit_type: str | None = Field(None, description="Administrative unit type")
|
||||
state: str = Field(..., description="State abbreviation")
|
||||
is_federal: bool = Field(..., description="Whether this is federal land")
|
||||
is_public: bool = Field(..., description="Whether this is publicly accessible")
|
||||
allows_public_access: bool = Field(
|
||||
..., description="Whether general public access is typically allowed"
|
||||
)
|
||||
|
||||
|
||||
class SurfaceManagementResult(BaseModel):
|
||||
@ -69,23 +71,29 @@ def _parse_sma(attrs: dict) -> LandManager:
|
||||
agency_code = attrs.get("ADMIN_AGENCY_CODE", "UND")
|
||||
dept_code = attrs.get("ADMIN_DEPT_CODE", "")
|
||||
|
||||
# Determine if federal and public
|
||||
# Federal departments (excluding private/state/local)
|
||||
federal_depts = {"DOI", "USDA", "DOD", "DOE"}
|
||||
public_agencies = {"BLM", "USFS", "NPS", "FWS", "USBR"}
|
||||
|
||||
is_federal = dept_code in federal_depts
|
||||
is_public = agency_code in public_agencies
|
||||
|
||||
# Agencies that generally allow public recreation access
|
||||
# Note: BIA (tribal) and DOD (military) are federal but restricted
|
||||
public_access_agencies = {"BLM", "USFS", "NPS", "FWS", "USBR"}
|
||||
allows_public_access = agency_code in public_access_agencies
|
||||
|
||||
return LandManager(
|
||||
agency_code=agency_code,
|
||||
agency_name=AGENCY_NAMES.get(agency_code, agency_code),
|
||||
department_code=dept_code if dept_code and dept_code != "Null" else None,
|
||||
department_name=DEPT_NAMES.get(dept_code) if dept_code else None,
|
||||
admin_unit_name=attrs.get("ADMIN_UNIT_NAME") if attrs.get("ADMIN_UNIT_NAME") != "Null" else None,
|
||||
admin_unit_type=attrs.get("ADMIN_UNIT_TYPE") if attrs.get("ADMIN_UNIT_TYPE") != "Null" else None,
|
||||
admin_unit_name=(
|
||||
attrs.get("ADMIN_UNIT_NAME") if attrs.get("ADMIN_UNIT_NAME") != "Null" else None
|
||||
),
|
||||
admin_unit_type=(
|
||||
attrs.get("ADMIN_UNIT_TYPE") if attrs.get("ADMIN_UNIT_TYPE") != "Null" else None
|
||||
),
|
||||
state=attrs.get("ADMIN_ST", ""),
|
||||
is_federal=is_federal,
|
||||
is_public=is_public,
|
||||
allows_public_access=allows_public_access,
|
||||
)
|
||||
|
||||
|
||||
@ -94,7 +102,7 @@ class SurfaceManagementMixin(MCPMixin):
|
||||
|
||||
@mcp_tool(
|
||||
name="get_land_manager",
|
||||
description="Determine who manages the land at given coordinates (BLM, Forest Service, NPS, Private, etc.)",
|
||||
description="Determine who manages the land (BLM, Forest Service, NPS, Private, etc.)",
|
||||
)
|
||||
async def get_land_manager(
|
||||
self,
|
||||
@ -132,10 +140,10 @@ class SurfaceManagementMixin(MCPMixin):
|
||||
status = []
|
||||
if mgr.is_federal:
|
||||
status.append("Federal")
|
||||
if mgr.is_public:
|
||||
status.append("Public")
|
||||
if mgr.allows_public_access:
|
||||
status.append("Public access")
|
||||
if status:
|
||||
lines.append(f"Status: {', '.join(status)} land")
|
||||
lines.append(f"Status: {', '.join(status)}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
@ -155,8 +163,13 @@ class SurfaceManagementMixin(MCPMixin):
|
||||
|
||||
async def _query_sma(self, latitude: float, longitude: float) -> SurfaceManagementResult:
|
||||
"""Query BLM SMA API for location."""
|
||||
results = await blm_client.identify(
|
||||
SMA_URL, latitude, longitude, f"all:{LAYER_SMA}"
|
||||
try:
|
||||
results = await blm_client.identify(SMA_URL, latitude, longitude, f"all:{LAYER_SMA}")
|
||||
except BLMAPIError as e:
|
||||
return SurfaceManagementResult(
|
||||
latitude=latitude,
|
||||
longitude=longitude,
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
if not results:
|
||||
|
||||
@ -11,7 +11,7 @@ All data is queried from official BLM ArcGIS REST services.
|
||||
|
||||
from fastmcp import FastMCP
|
||||
|
||||
from mcblmplss.mixins import PLSSMixin, SurfaceManagementMixin, MiningClaimsMixin
|
||||
from mcblmplss.mixins import MiningClaimsMixin, PLSSMixin, SurfaceManagementMixin
|
||||
|
||||
# Initialize FastMCP server
|
||||
mcp = FastMCP(
|
||||
@ -56,6 +56,7 @@ def main():
|
||||
"""Entry point for the mcblmplss MCP server."""
|
||||
try:
|
||||
from importlib.metadata import version
|
||||
|
||||
package_version = version("mcblmplss")
|
||||
except Exception:
|
||||
package_version = "dev"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user