-
-
Notifications
You must be signed in to change notification settings - Fork 351
feat: Add GeoAI MCP Server for AI agent integration #528
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: Add GeoAI MCP Server for AI agent integration #528
Conversation
This commit introduces a comprehensive Model Context Protocol (MCP) server that exposes GeoAI's geospatial AI capabilities to AI agents and LLM applications like Claude Desktop. Features: - 12 workflow-based tools covering segmentation, detection, change detection, data download, foundation models, and utilities - Pydantic schemas for input/output validation - Secure sandboxed file access with path traversal protection - Support for STDIO transport (Claude Desktop compatible) - Comprehensive test suite with pytest fixtures Tools included: - segment_objects_with_prompts: Text-prompted segmentation using SAM/GroundedSAM - auto_segment_image: Automatic segmentation without prompts - detect_and_classify_features: Object detection for buildings, vehicles, etc. - classify_land_cover: Pixel-wise land cover classification - detect_temporal_changes: Change detection between temporal images - download_satellite_imagery: Fetch NAIP, Sentinel-2, Landsat data - prepare_training_data: Create tiled training datasets - extract_features_with_foundation_model: DINOv3/Prithvi feature extraction - estimate_canopy_height: Vegetation height estimation from RGB - analyze_with_vision_language_model: VLM analysis using Moondream - clean_segmentation_results: Post-processing utilities - list_available_files: File discovery tool
for more information, see https://pre-commit.ci
|
Hi @giswqs! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Adds a new geoai-mcp-server Python package that implements an MCP server exposing GeoAI geospatial AI workflows (segmentation/detection/change detection/download/foundation-model utilities) for agent/LLM integrations (e.g., Claude Desktop).
Changes:
- Introduces the MCP server entrypoint with multiple async tools and result schemas.
- Adds supporting utilities (error types, input validation, sandboxed file management) and configuration management.
- Adds packaging/docs plus a pytest-based test suite and fixtures.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 38 comments.
Show a summary per file
| File | Description |
|---|---|
| geoai-mcp-server/src/geoai_mcp_server/server.py | Implements MCP tools and server entrypoint; integrates schemas and utilities. |
| geoai-mcp-server/src/geoai_mcp_server/config.py | Defines GeoAIConfig, env var loading, and logging helpers. |
| geoai-mcp-server/src/geoai_mcp_server/schemas/tool_schemas.py | Pydantic models/enums for tool inputs/outputs. |
| geoai-mcp-server/src/geoai_mcp_server/schemas/init.py | Re-exports schemas for external use. |
| geoai-mcp-server/src/geoai_mcp_server/utils/error_handling.py | Structured exceptions + error formatting utilities. |
| geoai-mcp-server/src/geoai_mcp_server/utils/file_management.py | Workspace file sandboxing helpers and output naming. |
| geoai-mcp-server/src/geoai_mcp_server/utils/validation.py | Input validation utilities (bbox, prompts, filenames, thresholds, etc.). |
| geoai-mcp-server/src/geoai_mcp_server/utils/init.py | Utility re-exports. |
| geoai-mcp-server/src/geoai_mcp_server/init.py | Package metadata and exports. |
| geoai-mcp-server/src/geoai_mcp_server/main.py | Enables python -m geoai_mcp_server execution. |
| geoai-mcp-server/tests/conftest.py | Adds pytest fixtures (temp dirs, sample geotiff/geojson, geoai module mocks). |
| geoai-mcp-server/tests/test_server.py | Adds async tests for server tools (mostly failure-path coverage). |
| geoai-mcp-server/tests/test_schemas.py | Adds schema validation tests. |
| geoai-mcp-server/tests/test_utils.py | Adds tests for utility modules. |
| geoai-mcp-server/tests/init.py | Marks tests as a package. |
| geoai-mcp-server/README.md | User-facing docs for setup, Claude Desktop integration, and tool catalog. |
| geoai-mcp-server/pyproject.toml | Packaging metadata + dev tooling (ruff/mypy/pytest). |
| geoai-mcp-server/pytest.ini | Pytest configuration. |
| geoai-mcp-server/.env.example | Example environment configuration. |
| geoai-mcp-server/.gitignore | Project-specific ignores (env, outputs, geospatial artifacts). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ) | ||
|
|
||
| full_input_path = get_safe_input_path(input_data.image_path, config) | ||
| if not full_input_path.exists(): | ||
| raise FileAccessError(f"Input file not found: {input_data.image_path}") | ||
|
|
||
| out_name = output_filename or generate_output_filename( | ||
| input_data.image_path, "auto_segmented", input_data.output_format.value | ||
| ) | ||
| output_path = validate_output_path(out_name, config) | ||
|
|
||
| logger.info(f"Auto-segmenting {full_input_path}") | ||
|
|
||
| sam_module = _get_geoai_module("sam") | ||
|
|
||
| # Initialize SAM |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
auto_segment_image passes output_path (derived from requested OutputFormat) directly into SamGeo.generate(output=...). If output_format is 'geojson'/'shapefile', this will likely produce an invalid output (SAM typically writes a raster mask). Consider always generating a raster mask (GeoTIFF) first and then converting/cleaning vector outputs separately so output_format is honored consistently.
geoai-mcp-server/tests/test_utils.py
Outdated
| error = TimeoutError("Operation exceeded 60 seconds", timeout=60) | ||
| assert error.timeout == 60 |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TimeoutError is constructed with an unexpected keyword argument (timeout) and the test asserts on error.timeout, but TimeoutError in utils/error_handling.py takes timeout_seconds and stores it in details["timeout_seconds"]. Update the test to use timeout_seconds and assert against error.details (or add a timeout attribute if that’s the intended API).
| error = TimeoutError("Operation exceeded 60 seconds", timeout=60) | |
| assert error.timeout == 60 | |
| error = TimeoutError("Operation exceeded 60 seconds", timeout_seconds=60) | |
| assert error.details["timeout_seconds"] == 60 |
geoai-mcp-server/tests/test_utils.py
Outdated
| validate_bbox(-122.5, 37.7, -122.4, 37.8) | ||
|
|
||
| def test_validate_bbox_invalid_lon(self): | ||
| """Test invalid longitude.""" | ||
| with pytest.raises(InputValidationError): | ||
| validate_bbox(-200, 37.7, -122.4, 37.8) | ||
|
|
||
| def test_validate_bbox_invalid_lat(self): | ||
| """Test invalid latitude.""" | ||
| with pytest.raises(InputValidationError): | ||
| validate_bbox(-122.5, 100, -122.4, 37.8) | ||
|
|
||
| def test_validate_bbox_inverted(self): | ||
| """Test inverted bounding box.""" | ||
| with pytest.raises(InputValidationError): | ||
| validate_bbox(-122.4, 37.7, -122.5, 37.8) # min > max |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Call to function validate_bbox with too many arguments; should be no more than 2.
| validate_bbox(-122.5, 37.7, -122.4, 37.8) | |
| def test_validate_bbox_invalid_lon(self): | |
| """Test invalid longitude.""" | |
| with pytest.raises(InputValidationError): | |
| validate_bbox(-200, 37.7, -122.4, 37.8) | |
| def test_validate_bbox_invalid_lat(self): | |
| """Test invalid latitude.""" | |
| with pytest.raises(InputValidationError): | |
| validate_bbox(-122.5, 100, -122.4, 37.8) | |
| def test_validate_bbox_inverted(self): | |
| """Test inverted bounding box.""" | |
| with pytest.raises(InputValidationError): | |
| validate_bbox(-122.4, 37.7, -122.5, 37.8) # min > max | |
| validate_bbox([-122.5, 37.7, -122.4, 37.8]) | |
| def test_validate_bbox_invalid_lon(self): | |
| """Test invalid longitude.""" | |
| with pytest.raises(InputValidationError): | |
| validate_bbox([-200, 37.7, -122.4, 37.8]) | |
| def test_validate_bbox_invalid_lat(self): | |
| """Test invalid latitude.""" | |
| with pytest.raises(InputValidationError): | |
| validate_bbox([-122.5, 100, -122.4, 37.8]) | |
| def test_validate_bbox_inverted(self): | |
| """Test inverted bounding box.""" | |
| with pytest.raises(InputValidationError): | |
| validate_bbox([-122.4, 37.7, -122.5, 37.8]) # min > max |
| def get_device(self) -> str: | ||
| """Get the actual device to use, resolving 'auto' if needed. | ||
|
|
||
| Returns: |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'except' clause does nothing but pass and there is no explanatory comment.
| out_ext = full_input_path.suffix | ||
| out_name = output_filename or generate_output_filename( | ||
| input_path, "cleaned", out_ext.lstrip(".") | ||
| ) |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'except' clause does nothing but pass and there is no explanatory comment.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
for more information, see https://pre-commit.ci
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- Remove unused imports: Literal from config.py, os from file_management.py, os from conftest.py, unused imports from test_server.py and test_utils.py - Fix path traversal vulnerability: Use Path.is_relative_to() instead of string startswith() for proper directory containment checks - Add comment for silent except clause in get_device() - Secure list_input_files(): Add pattern validation to prevent sandbox escape via malicious glob patterns, post-filter results to ensure containment - Update README: Fix environment variables table to match actual GeoAIConfig fields, update security section to accurately reflect implemented features - Fix test fixtures: Update sample_config to use actual GeoAIConfig fields - Fix test expectations: Update sanitize_filename tests and config tests to match actual implementation behavior - Export SegmentationModel and DetectionTarget in schemas/__init__.py
for more information, see https://pre-commit.ci
|
Hi @giswqs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 25 out of 25 changed files in this pull request and generated 20 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if output_type == "similarity_map" and reference_x is not None: | ||
| result = dino_module.compute_similarity_map( | ||
| str(full_input_path), | ||
| reference_point=(reference_x, reference_y), | ||
| output=str(output_path), |
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For output_type == "similarity_map", the guard only checks reference_x is not None but passes (reference_x, reference_y) even if reference_y is None. This can cause runtime errors in compute_similarity_map. Require both coordinates (or accept a single reference_point tuple) and return an InputValidationError when one is missing.
| class BoundingBox(BaseModel): | ||
| """Geographic bounding box in WGS84 coordinates.""" | ||
|
|
||
| min_lon: float = Field(..., description="Minimum longitude (west)", ge=-180, le=180) | ||
| min_lat: float = Field(..., description="Minimum latitude (south)", ge=-90, le=90) | ||
| max_lon: float = Field(..., description="Maximum longitude (east)", ge=-180, le=180) | ||
| max_lat: float = Field(..., description="Maximum latitude (north)", ge=-90, le=90) | ||
|
|
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BoundingBox enforces coordinate ranges but doesn’t enforce min_lon < max_lon / min_lat < max_lat. As a result, inverted bboxes will validate successfully and then fail later (or produce confusing behavior). Add a model-level validator to check ordering, or reuse the existing validate_bbox() utility to enforce these cross-field constraints.
| - name: Running pytest | ||
| run: | | ||
| uv run pytest . --verbose | ||
| export PYTHONPATH=$(pwd) | ||
| uv run pytest tests --verbose |
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CI now only runs pytest tests, which excludes the new geoai-mcp-server/tests suite. If the MCP server test suite is meant to gate merges (per PR description), add a separate step/job to install geoai-mcp-server (e.g., uv pip install -e geoai-mcp-server[dev]) and run its tests (pytest geoai-mcp-server/tests or cd geoai-mcp-server && pytest).
| - name: Running pytest | ||
| run: | | ||
| uv run pytest . --verbose | ||
| export PYTHONPATH=$(pwd) | ||
| uv run pytest tests --verbose |
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This workflow runs only the root tests/ directory; the new geoai-mcp-server/tests are not executed in CI. If those tests are intended to be part of the PR’s “comprehensive test suite”, add a step/job to install the MCP server subpackage and run its tests as well.
| - name: Running pytest | ||
| run: | | ||
| uv run pytest . --verbose | ||
| export PYTHONPATH=$(pwd) | ||
| uv run pytest tests --verbose |
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This workflow runs only the root tests/ directory; the new geoai-mcp-server/tests are not executed in CI. Consider adding a step/job to install geoai-mcp-server and run its tests to ensure MCP server changes are covered.
|
|
||
| import asyncio | ||
| import logging | ||
| import os |
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'os' is not used.
| import os |
| import asyncio | ||
| import logging | ||
| import os | ||
| import sys |
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'sys' is not used.
| import sys |
|
|
||
| from mcp.server.mcpserver import MCPServer | ||
|
|
||
| from .config import load_config, GeoAIConfig |
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'GeoAIConfig' is not used.
| from .config import load_config, GeoAIConfig | |
| from .config import load_config |
| validate_input_path, | ||
| validate_output_path, | ||
| get_safe_input_path, | ||
| list_input_files, | ||
| generate_output_filename, | ||
| ) |
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'validate_input_path' is not used.
| validate_input_path, | |
| validate_output_path, | |
| get_safe_input_path, | |
| list_input_files, | |
| generate_output_filename, | |
| ) | |
| validate_output_path, | |
| get_safe_input_path, | |
| list_input_files, | |
| generate_output_filename, | |
| ) |
| from .utils.validation import ( | ||
| validate_bbox, | ||
| validate_image_path, | ||
| validate_text_prompts, | ||
| ) |
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'validate_bbox' is not used.
Import of 'validate_image_path' is not used.
Import of 'validate_text_prompts' is not used.
| from .utils.validation import ( | |
| validate_bbox, | |
| validate_image_path, | |
| validate_text_prompts, | |
| ) |
This commit introduces a comprehensive Model Context Protocol (MCP) server that exposes GeoAI's geospatial AI capabilities to AI agents and LLM applications like Claude Desktop.
Features:
Tools included: