Skip to content
Merged
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
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ A comprehensive Python package for slope stability analysis using the SHALSTAB (
[![CI](https://github.com/federicogmz/shalstab-python/actions/workflows/ci.yml/badge.svg)](https://github.com/federicogmz/shalstab-python/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/federicogmz/shalstab-python/branch/main/graph/badge.svg)](https://codecov.io/gh/federicogmz/shalstab-python)
[![PyPI version](https://badge.fury.io/py/shalstab.svg)](https://pypi.org/project/shalstab/)
[![MCP Enabled](https://img.shields.io/badge/MCP-Enabled-blueviolet)](https://modelcontextprotocol.io)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)

## Table of Contents

- [Overview](#overview)
- [Installation](#installation)
- [Model Context Protocol (MCP) Integration](#model-context-protocol-mcp-integration)
- [Quick Start](#quick-start)
- [Scientific Background](#scientific-background)
- [Mathematical Framework](#mathematical-framework)
Expand Down Expand Up @@ -62,6 +64,76 @@ pip install -e .
pip install -r requirements.txt
```

## Model Context Protocol (MCP) Integration

> **Note:** MCP support is included in the development branch (`v1.1.0.dev0`). For local development, install with `pip install -e ".[mcp]"`.

`shalstab` natively supports the [Model Context Protocol (MCP)](https://modelcontextprotocol.io), allowing frontier AI agents (Claude Desktop, Claude Code, OpenAI Agents SDK) to execute deterministic slope stability physics directly from natural language. Note: MCP features require Python $\ge 3.10$.

### Quickstart with Claude Desktop

Add this to your `claude_desktop_config.json`:

**Directly from GitHub (Recommended):**
```json
{
"mcpServers": {
"shalstab": {
"command": "uvx",
"args": [
"--python", "3.11",
"--from", "shalstab[mcp] @ git+https://github.com/federicogmz/shalstab-python.git",
"shalstab-mcp"
]
}
}
}
```

**From local repository checkout:**
```json
{
"mcpServers": {
"shalstab": {
"command": "uv",
"args": [
"run",
"--python", "3.11",
"--directory", "/path/to/shalstab-python",
"--extra", "mcp",
"shalstab-mcp"
]
}
}
}
```

### Quickstart with OpenAI Agents SDK

*(Ensure `shalstab-mcp` is installed via `pip install -e ".[mcp]"` or from git `pip install "shalstab[mcp] @ git+https://github.com/federicogmz/shalstab-python.git"`, or run with `uvx`)*:

```python
import asyncio
from agents import Agent, Runner
from agents.mcp import MCPServerStdio

async def main():
# Connect to the local SHALSTAB MCP server via standard I/O (requires Python >= 3.10)
async with MCPServerStdio(params={"command": "shalstab-mcp"}) as server:
agent = Agent(name="GeohazardsAgent", mcp_servers=[server])
result = await Runner.run(
agent, "Evaluate slope stability for a 35 degree slope with 2 kPa cohesion and 32 degree friction angle"
)
print(result.final_output)

asyncio.run(main())
```

### Exposed Agent Tools
- `compute_infinite_slope_stability`: Analytical Factor of Safety (FS) and 4-class stability classification under dry, steady-state, and fully saturated regimes.
- `diagnose_topographic_wetness`: Steady-state hydrologic saturation and critical recharge threshold $\log_{10}(q/T)$ (and natural $\ln(q/T)$) for shallow failure per Montgomery & Dietrich (1994).
*Note on formulation:* Analytical critical recharge strictly implements Montgomery & Dietrich (1994) Eq. 5, matching `Analyzer.calculate_log_qt`.

## Training data

The easiest way to test if SHALSTAB is installed correctly or to get familiar with its implementation is to use the provided training data:
Expand Down
13 changes: 11 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "shalstab"
version = "1.0.3"
version = "1.1.0.dev0"
authors = [
{name = "Federico Gómez", email = "fjgomezc@eafit.edu.co"},
]
Expand Down Expand Up @@ -55,7 +55,12 @@ dependencies = [
]

[project.optional-dependencies]
test = ["pytest>=7.0.0", "pytest-cov>=4.0.0"]
test = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
"mcp>=1.2.0; python_version>='3.10'",
]
mcp = ["mcp>=1.2.0; python_version>='3.10'"]
dev = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
Expand All @@ -73,8 +78,12 @@ all = [
"sphinx-rtd-theme>=1.0.0",
"jupyter>=1.0.0",
"pre-commit>=2.20.0",
"mcp>=1.2.0; python_version>='3.10'",
]

[project.scripts]
shalstab-mcp = "shalstab.mcp_server:main"

[project.urls]
Homepage = "https://github.com/federicogmz/shalstab-python"
Repository = "https://github.com/federicogmz/shalstab-python.git"
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

setup(
name="shalstab",
version="1.0.0",
version="1.1.0.dev0",
author="Federico Gómez",
author_email="fjgomezc@eafit.edu.co",
description="Shallow Landsliding STABility (SHALSTAB) model for slope stability analysis",
Expand Down
39 changes: 32 additions & 7 deletions shalstab/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,39 @@

"""

from .slope_analyzer import Analyzer
import importlib.util
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
from .slope_analyzer import Analyzer

__version__ = "1.1.0.dev0"
__all__ = ["Analyzer", "__version__"]

# Training data
try:
from .training_data import training_dem, training_geology
if importlib.util.find_spec("shalstab.training_data") is not None:
__all__.extend(["training_dem", "training_geology"])
except Exception:
pass


def __getattr__(name: str) -> Any:
"""Lazy import heavy modules only when accessed to keep startup fast and lean."""
if name == "Analyzer":
from .slope_analyzer import Analyzer

return Analyzer
elif name in ("training_dem", "training_geology"):
try:
from . import training_data

return getattr(training_data, name)
except ImportError:
raise AttributeError(
f"module {__name__!r} has no attribute {name!r}"
) from None
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

__all__ = ["Analyzer", "training_dem", "training_geology"]
except ImportError:
__all__ = ["Analyzer"]

__version__ = "1.0.3"
def __dir__() -> list[str]:
return sorted(set(globals()) | set(__all__))
Loading
Loading