Skip to content

Conversation

@ram-from-tvl
Copy link
Contributor

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

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
Copilot AI review requested due to automatic review settings February 9, 2026 09:43
@ram-from-tvl
Copy link
Contributor Author

Hi @giswqs!
I’ve submitted a PR for a locally hosted MCP server for this project. I’d appreciate it if you could take a look at the code when you have a moment. Looking forward to your feedback!

Copy link
Contributor

Copilot AI left a 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.

Comment on lines +317 to +332
)

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
Copy link

Copilot AI Feb 9, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines 58 to 59
error = TimeoutError("Operation exceeded 60 seconds", timeout=60)
assert error.timeout == 60
Copy link

Copilot AI Feb 9, 2026

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).

Suggested change
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

Copilot uses AI. Check for mistakes.
Comment on lines 68 to 83
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
Copy link

Copilot AI Feb 9, 2026

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
def get_device(self) -> str:
"""Get the actual device to use, resolving 'auto' if needed.

Returns:
Copy link

Copilot AI Feb 9, 2026

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.

Copilot uses AI. Check for mistakes.
out_ext = full_input_path.suffix
out_name = output_filename or generate_output_filename(
input_path, "cleaned", out_ext.lstrip(".")
)
Copy link

Copilot AI Feb 9, 2026

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.

Copilot uses AI. Check for mistakes.
ram-from-tvl and others added 11 commits February 9, 2026 19:25
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>
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
@ram-from-tvl
Copy link
Contributor Author

Hi @giswqs
All tests are passing. Please let me know if the code looks good.
Thankyou!

Copy link
Contributor

Copilot AI left a 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.

Comment on lines +963 to +967
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),
Copy link

Copilot AI Feb 10, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +85
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)

Copy link

Copilot AI Feb 10, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines 51 to +54
- name: Running pytest
run: |
uv run pytest . --verbose
export PYTHONPATH=$(pwd)
uv run pytest tests --verbose
Copy link

Copilot AI Feb 10, 2026

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).

Copilot uses AI. Check for mistakes.
Comment on lines 49 to +52
- name: Running pytest
run: |
uv run pytest . --verbose
export PYTHONPATH=$(pwd)
uv run pytest tests --verbose
Copy link

Copilot AI Feb 10, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines 51 to +54
- name: Running pytest
run: |
uv run pytest . --verbose
export PYTHONPATH=$(pwd)
uv run pytest tests --verbose
Copy link

Copilot AI Feb 10, 2026

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.

Copilot uses AI. Check for mistakes.

import asyncio
import logging
import os
Copy link

Copilot AI Feb 10, 2026

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.

Suggested change
import os

Copilot uses AI. Check for mistakes.
import asyncio
import logging
import os
import sys
Copy link

Copilot AI Feb 10, 2026

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.

Suggested change
import sys

Copilot uses AI. Check for mistakes.

from mcp.server.mcpserver import MCPServer

from .config import load_config, GeoAIConfig
Copy link

Copilot AI Feb 10, 2026

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.

Suggested change
from .config import load_config, GeoAIConfig
from .config import load_config

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +80
validate_input_path,
validate_output_path,
get_safe_input_path,
list_input_files,
generate_output_filename,
)
Copy link

Copilot AI Feb 10, 2026

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.

Suggested change
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,
)

Copilot uses AI. Check for mistakes.
Comment on lines +81 to +85
from .utils.validation import (
validate_bbox,
validate_image_path,
validate_text_prompts,
)
Copy link

Copilot AI Feb 10, 2026

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.

Suggested change
from .utils.validation import (
validate_bbox,
validate_image_path,
validate_text_prompts,
)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant