adcirc-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,45 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ *.egg
7
+ dist/
8
+ build/
9
+ *.whl
10
+
11
+ # Virtual environments
12
+ .venv/
13
+ venv/
14
+ env/
15
+
16
+ # Testing
17
+ .pytest_cache/
18
+ .coverage
19
+ htmlcov/
20
+ .tox/
21
+
22
+ # IDE
23
+ .idea/
24
+ .vscode/
25
+ *.swp
26
+ *.swo
27
+ *~
28
+
29
+ # OS
30
+ .DS_Store
31
+ Thumbs.db
32
+
33
+ # Linting
34
+ .ruff_cache/
35
+ .mypy_cache/
36
+
37
+ # Lock files (each server manages its own)
38
+ uv.lock
39
+
40
+ # Environment
41
+ .env
42
+ .env.local
43
+
44
+ # Registry tokens
45
+ .mcpregistry_*
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Mansur Ali Jisan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: adcirc-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for ADCIRC model setup debugging, parameter lookup, and configuration validation
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: httpx>=0.27.0
8
+ Requires-Dist: mcp[cli]>=1.0.0
9
+ Requires-Dist: pydantic>=2.0.0
@@ -0,0 +1,65 @@
1
+ # adcirc-mcp
2
+
3
+ <!-- mcp-name: io.github.mansurjisan/adcirc-mcp -->
4
+
5
+ MCP server for ADCIRC model setup debugging, parameter lookup, and configuration validation.
6
+
7
+ Unlike the other ocean-mcp servers that query remote NOAA APIs, adcirc-mcp **parses local model input files** and provides **embedded domain knowledge** to help debug and understand ADCIRC configurations.
8
+
9
+ ## Tools (10)
10
+
11
+ ### Parameter Reference
12
+ - **`adcirc_explain_parameter`** — Look up any fort.15 parameter, NWS value, tidal constituent, or nodal attribute
13
+ - **`adcirc_list_parameters`** — List all parameters grouped by category
14
+
15
+ ### File Parsing
16
+ - **`adcirc_parse_fort15`** — Parse fort.15 control file into structured summary
17
+ - **`adcirc_parse_fort14`** — Parse fort.14 mesh file header (node/element counts, boundaries)
18
+ - **`adcirc_parse_fort13`** — Parse fort.13 nodal attributes (names, defaults, non-default counts)
19
+ - **`adcirc_parse_fort22`** — Parse fort.22 meteorological forcing header
20
+
21
+ ### Validation & Debugging
22
+ - **`adcirc_validate_config`** — Comprehensive validation with CFL check and cross-file verification
23
+ - **`adcirc_diagnose_error`** — Match error text against known ADCIRC failure patterns
24
+
25
+ ### Documentation
26
+ - **`adcirc_fetch_docs`** — Fetch pages from the ADCIRC wiki
27
+ - **`adcirc_search_docs`** — Search the ADCIRC wiki
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ # Using uvx (recommended)
33
+ uvx adcirc-mcp
34
+
35
+ # Or install from source
36
+ cd servers/adcirc-mcp
37
+ uv sync
38
+ ```
39
+
40
+ ## Configuration
41
+
42
+ Add to your MCP client config:
43
+
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "adcirc": {
48
+ "command": "uvx",
49
+ "args": ["adcirc-mcp"]
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ ## Development
56
+
57
+ ```bash
58
+ cd servers/adcirc-mcp
59
+ uv sync --group dev
60
+ uv run pytest tests/ --ignore=tests/test_live.py --ignore=tests/test_mcp_protocol.py -v
61
+ ```
62
+
63
+ ## License
64
+
65
+ MIT
@@ -0,0 +1,28 @@
1
+ [project]
2
+ name = "adcirc-mcp"
3
+ version = "0.1.0"
4
+ description = "MCP server for ADCIRC model setup debugging, parameter lookup, and configuration validation"
5
+ requires-python = ">=3.11"
6
+ dependencies = [
7
+ "mcp[cli]>=1.0.0",
8
+ "httpx>=0.27.0",
9
+ "pydantic>=2.0.0",
10
+ ]
11
+
12
+ [project.scripts]
13
+ adcirc-mcp = "adcirc_mcp.server:main"
14
+
15
+ [build-system]
16
+ requires = ["hatchling"]
17
+ build-backend = "hatchling.build"
18
+
19
+ [tool.pytest.ini_options]
20
+ testpaths = ["tests"]
21
+ asyncio_mode = "auto"
22
+
23
+ [dependency-groups]
24
+ dev = [
25
+ "pytest>=8.0.0",
26
+ "pytest-asyncio>=0.24.0",
27
+ "respx>=0.22.0",
28
+ ]
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.mansurjisan/adcirc-mcp",
4
+ "title": "ADCIRC Model Setup MCP Server",
5
+ "description": "ADCIRC model configuration debugging, parameter lookup, and validation",
6
+ "version": "0.1.0",
7
+ "repository": {
8
+ "url": "https://github.com/mansurjisan/ocean-mcp",
9
+ "source": "github"
10
+ },
11
+ "packages": [
12
+ {
13
+ "registryType": "pypi",
14
+ "identifier": "adcirc-mcp",
15
+ "version": "0.1.0",
16
+ "runtimeHint": "uvx",
17
+ "transport": {
18
+ "type": "stdio"
19
+ }
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,8 @@
1
+ startCommand:
2
+ type: stdio
3
+ configSchema:
4
+ type: object
5
+ required: []
6
+ properties: {}
7
+ commandFunction: |-
8
+ (config) => ({ command: 'uvx', args: ['adcirc-mcp'] })
File without changes
@@ -0,0 +1,5 @@
1
+ """Allow running with python -m adcirc_mcp."""
2
+
3
+ from .server import main
4
+
5
+ main()
@@ -0,0 +1,118 @@
1
+ """Dual-purpose client: local file reader + ADCIRC wiki documentation fetcher."""
2
+
3
+ import re
4
+
5
+ import httpx
6
+
7
+ ADCIRC_WIKI_BASE = "https://wiki.adcirc.org"
8
+ WIKI_API_URL = f"{ADCIRC_WIKI_BASE}/w/api.php"
9
+
10
+ MAX_FILE_SIZE = 100 * 1024 * 1024 # 100 MB
11
+
12
+
13
+ class ADCIRCClientError(Exception):
14
+ """Custom exception for ADCIRC client errors."""
15
+
16
+ pass
17
+
18
+
19
+ class ADCIRCClient:
20
+ """Async client for local file reading and ADCIRC wiki access."""
21
+
22
+ def __init__(self) -> None:
23
+ self._client: httpx.AsyncClient | None = None
24
+
25
+ async def _get_client(self) -> httpx.AsyncClient:
26
+ if self._client is None or self._client.is_closed:
27
+ self._client = httpx.AsyncClient(timeout=30.0)
28
+ return self._client
29
+
30
+ @staticmethod
31
+ def read_file(file_path: str) -> str:
32
+ """Read a local file with size limit."""
33
+ import os
34
+
35
+ size = os.path.getsize(file_path)
36
+ if size > MAX_FILE_SIZE:
37
+ raise ADCIRCClientError(
38
+ f"File too large ({size / 1024 / 1024:.1f} MB). "
39
+ f"Max is {MAX_FILE_SIZE / 1024 / 1024:.0f} MB."
40
+ )
41
+ with open(file_path) as f:
42
+ return f.read()
43
+
44
+ @staticmethod
45
+ def read_file_header(file_path: str, max_lines: int = 100) -> str:
46
+ """Read only the first N lines of a file."""
47
+ lines = []
48
+ with open(file_path) as f:
49
+ for i, line in enumerate(f):
50
+ if i >= max_lines:
51
+ break
52
+ lines.append(line)
53
+ return "".join(lines)
54
+
55
+ async def fetch_wiki_page(self, page_title: str) -> str:
56
+ """Fetch a wiki page and return its content as plain text."""
57
+ client = await self._get_client()
58
+ params = {
59
+ "action": "parse",
60
+ "page": page_title,
61
+ "prop": "text",
62
+ "format": "json",
63
+ }
64
+ response = await client.get(WIKI_API_URL, params=params)
65
+ response.raise_for_status()
66
+ data = response.json()
67
+
68
+ if "error" in data:
69
+ raise ADCIRCClientError(
70
+ f"Wiki page not found: {data['error'].get('info', page_title)}"
71
+ )
72
+
73
+ html = data.get("parse", {}).get("text", {}).get("*", "")
74
+ return strip_html_to_text(html)
75
+
76
+ async def search_wiki(self, query: str, limit: int = 10) -> list[dict]:
77
+ """Search the ADCIRC wiki and return matching pages."""
78
+ client = await self._get_client()
79
+ params = {
80
+ "action": "query",
81
+ "list": "search",
82
+ "srsearch": query,
83
+ "srlimit": str(limit),
84
+ "format": "json",
85
+ }
86
+ response = await client.get(WIKI_API_URL, params=params)
87
+ response.raise_for_status()
88
+ data = response.json()
89
+
90
+ results = []
91
+ for item in data.get("query", {}).get("search", []):
92
+ results.append(
93
+ {
94
+ "title": item.get("title", ""),
95
+ "snippet": strip_html_to_text(item.get("snippet", "")),
96
+ "url": f"{ADCIRC_WIKI_BASE}/wiki/{item.get('title', '').replace(' ', '_')}",
97
+ }
98
+ )
99
+ return results
100
+
101
+ async def close(self) -> None:
102
+ if self._client and not self._client.is_closed:
103
+ await self._client.aclose()
104
+
105
+
106
+ def strip_html_to_text(html: str) -> str:
107
+ """Strip HTML tags and decode entities to plain text."""
108
+ text = re.sub(r"<script[^>]*>.*?</script>", "", html, flags=re.DOTALL)
109
+ text = re.sub(r"<style[^>]*>.*?</style>", "", text, flags=re.DOTALL)
110
+ text = re.sub(r"<[^>]+>", " ", text)
111
+ text = text.replace("&amp;", "&")
112
+ text = text.replace("&lt;", "<")
113
+ text = text.replace("&gt;", ">")
114
+ text = text.replace("&quot;", '"')
115
+ text = text.replace("&#039;", "'")
116
+ text = text.replace("&nbsp;", " ")
117
+ text = re.sub(r"\s+", " ", text).strip()
118
+ return text