mock-gkn-hpc 0.3.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.
- mock_gkn_hpc-0.3.0/.github/workflows/publish.yml +53 -0
- mock_gkn_hpc-0.3.0/.gitignore +13 -0
- mock_gkn_hpc-0.3.0/.mcp.json +9 -0
- mock_gkn_hpc-0.3.0/.python-version +1 -0
- mock_gkn_hpc-0.3.0/CLAUDE.md +42 -0
- mock_gkn_hpc-0.3.0/LICENSE +21 -0
- mock_gkn_hpc-0.3.0/PKG-INFO +47 -0
- mock_gkn_hpc-0.3.0/README.md +28 -0
- mock_gkn_hpc-0.3.0/pyproject.toml +33 -0
- mock_gkn_hpc-0.3.0/src/mock_gkn_hpc/__init__.py +4 -0
- mock_gkn_hpc-0.3.0/src/mock_gkn_hpc/py.typed +0 -0
- mock_gkn_hpc-0.3.0/src/mock_gkn_hpc/server.py +186 -0
- mock_gkn_hpc-0.3.0/src/mock_gkn_hpc/templates/results.rst.tmpl +28 -0
- mock_gkn_hpc-0.3.0/uv.lock +1035 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: Test and Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
release:
|
|
9
|
+
types: [published]
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: ["3.14"]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v4
|
|
23
|
+
|
|
24
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
25
|
+
run: uv python install ${{ matrix.python-version }}
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: uv sync
|
|
29
|
+
|
|
30
|
+
- name: Smoke import
|
|
31
|
+
run: uv run python -c "import mock_gkn_hpc; print(mock_gkn_hpc.__version__)"
|
|
32
|
+
|
|
33
|
+
publish:
|
|
34
|
+
needs: test
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
if: github.event_name == 'release'
|
|
37
|
+
permissions:
|
|
38
|
+
id-token: write
|
|
39
|
+
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
|
|
43
|
+
- name: Install uv
|
|
44
|
+
uses: astral-sh/setup-uv@v4
|
|
45
|
+
|
|
46
|
+
- name: Set up Python
|
|
47
|
+
run: uv python install 3.14
|
|
48
|
+
|
|
49
|
+
- name: Build package
|
|
50
|
+
run: uv build
|
|
51
|
+
|
|
52
|
+
- name: Publish to PyPI
|
|
53
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
A FastMCP server that **mocks** GKN's HPC for Ansys / Finite Element analysis. It is an external demo that lets clients exercise the full submit → poll → fetch-artifacts loop against a fake HPC, with no real solver, queue, or cluster behind it.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
Packaged distribution under `src/mock_gkn_hpc/`. Single module `server.py` (~190 lines of FastMCP). The model is intentionally tiny:
|
|
12
|
+
|
|
13
|
+
- **Filesystem is the state store.** No in-memory registry. `RUNS_DIR/<job_id>/` exists ⇒ job submitted; `results.rst` present ⇒ ready; older than `JOB_TTL_SECONDS` ⇒ expired. `_job_status()` derives status from directory + `results.rst` mtime — `log.txt` is *not* a readiness sentinel because it's created at submit time and grown during the run.
|
|
14
|
+
- **`RUNS_DIR` defaults to `gettempdir()/mock-gkn-hpc`.** Overridable via `--runs-dir` CLI flag or `MOCK_GKN_HPC_RUNS_DIR` env var. Set in `main()` before `mcp.run()` — the module-level `RUNS_DIR` is reassigned, so all tools/resources see the override.
|
|
15
|
+
- **Async submit, streaming log, terminal results.** `submit_ansys_run` returns immediately with a `job_id` and two `ansys://...` URIs. A background `asyncio.create_task(_finish(job_id))` walks `LOG_SCHEDULE` (a list of `(sleep, line)` tuples summing to `MOCK_RUN_SECONDS = 30s`) appending each line to `log.txt`, then renders `results.rst` from `src/mock_gkn_hpc/templates/results.rst.tmpl` with current date/time/job_id substituted in.
|
|
16
|
+
- **Resources are templated**: `ansys://{job_id}/log` and `ansys://{job_id}/results`. The log resource serves whatever has been written so far — clients can poll it to watch progress mid-run. The results resource 404s ("not found or still running") until `_finish` writes it at t≈30s. Both self-delete + 404 when past TTL.
|
|
17
|
+
- **`age_seconds` in `list_jobs` is intentionally inconsistent**: time-since-submit while pending (dir mtime), time-since-completion once ready (`results.rst` mtime). Not a bug — derives from the dir-vs-file state model.
|
|
18
|
+
- `submit_ansys_run`'s `input_file` and `version` are part of the tool schema for client realism but unused by the mock.
|
|
19
|
+
|
|
20
|
+
The `templates/` directory ships *inside* the wheel (`hatchling` includes everything under `src/mock_gkn_hpc/`), so `Path(__file__).parent / "templates"` resolves identically in editable and installed mode.
|
|
21
|
+
|
|
22
|
+
## Dev workflow
|
|
23
|
+
|
|
24
|
+
The server is registered via project-scoped `.mcp.json` as a stdio server invoking `uv run mock-gkn-hpc` (the entry-point script that `uv sync` installs in editable mode). Claude Code spawns it on connect; **after every edit to `server.py`, manually reconnect via `/mcp` in Claude Code** to pick up the change.
|
|
25
|
+
|
|
26
|
+
Avoid `fastmcp call ... submit_ansys_run`: each call is a one-shot subprocess that exits before the 30s `_finish()` background task fires, leaving zombie pending jobs in `RUNS_DIR`. Either go through Claude Code (long-lived stdio session) or write a client that holds the connection open past `MOCK_RUN_SECONDS`.
|
|
27
|
+
|
|
28
|
+
Useful commands:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uv sync # editable install
|
|
32
|
+
uv run mock-gkn-hpc --help # show CLI flags
|
|
33
|
+
uv run mock-gkn-hpc # run the server over stdio
|
|
34
|
+
uv build # produce wheel + sdist in dist/
|
|
35
|
+
uvx --from ./dist/mock_gkn_hpc-*.whl mock-gkn-hpc --help # smoke-test the built wheel
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
No tests, no linter configured.
|
|
39
|
+
|
|
40
|
+
## Gotchas
|
|
41
|
+
|
|
42
|
+
- Python ≥ 3.14. Per global guidance, use 3.14 syntax — no `from typing import Optional/List/Dict`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alejandro Pradas Gómez
|
|
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,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mock-gkn-hpc
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Public MCP mock of GKN's internal HPC server for Ansys/FEA analysis
|
|
5
|
+
Project-URL: Homepage, https://github.com/alex-pradas/mock-gkn-hpc
|
|
6
|
+
Project-URL: Repository, https://github.com/alex-pradas/mock-gkn-hpc
|
|
7
|
+
Author-email: Alejandro Pradas Gómez <alex@pradas.eu>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: ansys,fastmcp,fea,gkn,hpc,mcp
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Requires-Python: >=3.14
|
|
17
|
+
Requires-Dist: fastmcp>=3.2.4
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# mock-gkn-hpc
|
|
21
|
+
|
|
22
|
+
[](https://doi.org/10.5281/zenodo.18836517)
|
|
23
|
+
|
|
24
|
+
A public MCP **mock** of GKN's internal HPC server for Ansys / Finite Element analysis. It lets external clients exercise the full submit → poll → fetch-artifacts loop against a fake HPC — no real solver, queue, or cluster behind it.
|
|
25
|
+
|
|
26
|
+
This is a sample MCP server developed alongside the DUCTILE agentic orchestration paper. See the [DUCTILE repository](https://github.com/alex-pradas/DUCTILE) or the paper (DOI: TBD) for context.
|
|
27
|
+
|
|
28
|
+
## Install and run
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uvx mock-gkn-hpc
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or wire it into your MCP client config:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"mcpServers": {
|
|
39
|
+
"mock-gkn-hpc": {
|
|
40
|
+
"command": "uvx",
|
|
41
|
+
"args": ["mock-gkn-hpc"]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Job artifacts default to a system tempdir (`gettempdir()/mock-gkn-hpc`). Override with `--runs-dir /custom/path` or the `MOCK_GKN_HPC_RUNS_DIR` env var.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# mock-gkn-hpc
|
|
2
|
+
|
|
3
|
+
[](https://doi.org/10.5281/zenodo.18836517)
|
|
4
|
+
|
|
5
|
+
A public MCP **mock** of GKN's internal HPC server for Ansys / Finite Element analysis. It lets external clients exercise the full submit → poll → fetch-artifacts loop against a fake HPC — no real solver, queue, or cluster behind it.
|
|
6
|
+
|
|
7
|
+
This is a sample MCP server developed alongside the DUCTILE agentic orchestration paper. See the [DUCTILE repository](https://github.com/alex-pradas/DUCTILE) or the paper (DOI: TBD) for context.
|
|
8
|
+
|
|
9
|
+
## Install and run
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
uvx mock-gkn-hpc
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or wire it into your MCP client config:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"mock-gkn-hpc": {
|
|
21
|
+
"command": "uvx",
|
|
22
|
+
"args": ["mock-gkn-hpc"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Job artifacts default to a system tempdir (`gettempdir()/mock-gkn-hpc`). Override with `--runs-dir /custom/path` or the `MOCK_GKN_HPC_RUNS_DIR` env var.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "mock-gkn-hpc"
|
|
3
|
+
version = "0.3.0"
|
|
4
|
+
description = "Public MCP mock of GKN's internal HPC server for Ansys/FEA analysis"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [{ name = "Alejandro Pradas Gómez", email = "alex@pradas.eu" }]
|
|
9
|
+
keywords = ["mcp", "ansys", "fea", "hpc", "fastmcp", "gkn"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 3 - Alpha",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.14",
|
|
16
|
+
]
|
|
17
|
+
dependencies = [
|
|
18
|
+
"fastmcp>=3.2.4",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/alex-pradas/mock-gkn-hpc"
|
|
23
|
+
Repository = "https://github.com/alex-pradas/mock-gkn-hpc"
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
mock-gkn-hpc = "mock_gkn_hpc.server:main"
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["hatchling"]
|
|
30
|
+
build-backend = "hatchling.build"
|
|
31
|
+
|
|
32
|
+
[tool.hatch.build.targets.wheel]
|
|
33
|
+
packages = ["src/mock_gkn_hpc"]
|
|
File without changes
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import asyncio
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import tempfile
|
|
6
|
+
import time
|
|
7
|
+
import uuid
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from fastmcp import FastMCP
|
|
12
|
+
from pydantic import FilePath
|
|
13
|
+
|
|
14
|
+
mcp = FastMCP(
|
|
15
|
+
"HPC at GKN, external demo",
|
|
16
|
+
instructions="Simulates the HPC for Finite Elements analyisis at GKN",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
DEFAULT_RUNS_DIR = Path(tempfile.gettempdir()) / "mock-gkn-hpc"
|
|
20
|
+
RUNS_DIR = DEFAULT_RUNS_DIR
|
|
21
|
+
TEMPLATES_DIR = Path(__file__).parent / "templates"
|
|
22
|
+
JOB_TTL_SECONDS = 3600
|
|
23
|
+
MOCK_RUN_SECONDS = 30
|
|
24
|
+
|
|
25
|
+
# (sleep_before_writing_seconds, line). Sleeps sum to MOCK_RUN_SECONDS.
|
|
26
|
+
LOG_SCHEDULE: list[tuple[float, str]] = [
|
|
27
|
+
(0.0, "ANSYS Mechanical 2025R1 starting..."),
|
|
28
|
+
(0.2, "License: ANSYS Mechanical Enterprise -- checked out"),
|
|
29
|
+
(0.3, "Reading input file: model.cdb"),
|
|
30
|
+
(0.5, "Mesh statistics: 12847 nodes, 8432 elements, 38541 dofs"),
|
|
31
|
+
(0.5, "Material: Structural Steel (E=200 GPa, nu=0.30)"),
|
|
32
|
+
(0.5, "Boundary conditions: 1 fixed support, 2 force loads"),
|
|
33
|
+
(0.5, ""),
|
|
34
|
+
(0.0, "--- SOLUTION ---"),
|
|
35
|
+
(0.5, "Solver: Sparse Direct"),
|
|
36
|
+
(0.5, "Reordering equations (METIS)..."),
|
|
37
|
+
(1.0, "Equation reordering complete; matrix non-zeros = 1.84M, factor non-zeros = 8.2M"),
|
|
38
|
+
(1.0, "Symbolic factorization..."),
|
|
39
|
+
(2.0, "Numeric factorization (LDL^T)..."),
|
|
40
|
+
(5.0, "Factor complete; peak memory = 412 MB"),
|
|
41
|
+
(2.0, "Forward/back substitution..."),
|
|
42
|
+
(4.0, "Solver complete; max residual = 4.21e-09"),
|
|
43
|
+
(2.0, "*** LOAD STEP 1 SUBSTEP 1 COMPLETED. CUM ITER = 1 ***"),
|
|
44
|
+
(2.0, ""),
|
|
45
|
+
(0.0, "--- POSTPROCESSING ---"),
|
|
46
|
+
(1.0, "Computing element results..."),
|
|
47
|
+
(2.0, "Max von Mises stress: 187.4 MPa @ node 4521"),
|
|
48
|
+
(1.0, "Max displacement: 0.842 mm @ node 9183"),
|
|
49
|
+
(1.0, "Reaction force at fixed support: (-2104.3, 8.2, -45.1) N"),
|
|
50
|
+
(1.0, "Writing results database..."),
|
|
51
|
+
(1.5, "Wrote results.rst"),
|
|
52
|
+
(0.5, "Solution complete. CPU time: 28.7s, elapsed: 30.1s"),
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _render_results(job_id: str, dst: Path) -> None:
|
|
57
|
+
tmpl = (TEMPLATES_DIR / "results.rst.tmpl").read_text()
|
|
58
|
+
now = datetime.now()
|
|
59
|
+
dst.write_text(tmpl.format(
|
|
60
|
+
run_date=now.strftime("%Y-%m-%d"),
|
|
61
|
+
run_time=now.strftime("%H:%M:%S"),
|
|
62
|
+
job_id=job_id,
|
|
63
|
+
))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
async def _finish(job_id: str) -> None:
|
|
67
|
+
job_dir = RUNS_DIR / job_id
|
|
68
|
+
job_dir.mkdir(parents=True, exist_ok=True)
|
|
69
|
+
log = job_dir / "log.txt"
|
|
70
|
+
log.write_text("")
|
|
71
|
+
for delay, line in LOG_SCHEDULE:
|
|
72
|
+
if delay:
|
|
73
|
+
await asyncio.sleep(delay)
|
|
74
|
+
with log.open("a") as f:
|
|
75
|
+
f.write(line + "\n")
|
|
76
|
+
_render_results(job_id, job_dir / "results.rst")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _job_status(job_dir: Path) -> tuple[str, float]:
|
|
80
|
+
results = job_dir / "results.rst"
|
|
81
|
+
if not results.exists():
|
|
82
|
+
return "pending", time.time() - job_dir.stat().st_mtime
|
|
83
|
+
age = time.time() - results.stat().st_mtime
|
|
84
|
+
if age > JOB_TTL_SECONDS:
|
|
85
|
+
return "expired", age
|
|
86
|
+
return "ready", age
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@mcp.tool
|
|
90
|
+
async def submit_ansys_run(
|
|
91
|
+
input_file: FilePath,
|
|
92
|
+
version: str = "2025r1",
|
|
93
|
+
) -> dict:
|
|
94
|
+
"""Submits run to HPC. Returns immediately; log and results become
|
|
95
|
+
available at the returned URIs after ~30s. Resources expire after 1h."""
|
|
96
|
+
job_id = uuid.uuid4().hex[:8]
|
|
97
|
+
(RUNS_DIR / job_id).mkdir(parents=True, exist_ok=True)
|
|
98
|
+
asyncio.create_task(_finish(job_id))
|
|
99
|
+
return {
|
|
100
|
+
"status": "submitted",
|
|
101
|
+
"job_id": job_id,
|
|
102
|
+
"log_uri": f"ansys://{job_id}/log",
|
|
103
|
+
"results_uri": f"ansys://{job_id}/results",
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@mcp.tool
|
|
108
|
+
def status() -> str:
|
|
109
|
+
return "online"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@mcp.tool
|
|
113
|
+
def list_jobs() -> list[dict]:
|
|
114
|
+
"""Lists every submitted job with its current status (pending/ready/expired) and age in seconds."""
|
|
115
|
+
if not RUNS_DIR.exists():
|
|
116
|
+
return []
|
|
117
|
+
jobs = []
|
|
118
|
+
for job_dir in sorted(RUNS_DIR.iterdir()):
|
|
119
|
+
if not job_dir.is_dir():
|
|
120
|
+
continue
|
|
121
|
+
st, age = _job_status(job_dir)
|
|
122
|
+
jobs.append({
|
|
123
|
+
"job_id": job_dir.name,
|
|
124
|
+
"status": st,
|
|
125
|
+
"age_seconds": round(age, 1),
|
|
126
|
+
"log_uri": f"ansys://{job_dir.name}/log",
|
|
127
|
+
"results_uri": f"ansys://{job_dir.name}/results",
|
|
128
|
+
})
|
|
129
|
+
return jobs
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@mcp.tool
|
|
133
|
+
def delete_results() -> dict:
|
|
134
|
+
"""Deletes every job's log and results files."""
|
|
135
|
+
if not RUNS_DIR.exists():
|
|
136
|
+
return {"deleted": 0}
|
|
137
|
+
count = sum(1 for p in RUNS_DIR.iterdir() if p.is_dir())
|
|
138
|
+
shutil.rmtree(RUNS_DIR)
|
|
139
|
+
return {"deleted": count}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _read_artifact(job_id: str, name: str) -> str:
|
|
143
|
+
path = RUNS_DIR / job_id / name
|
|
144
|
+
if not path.exists():
|
|
145
|
+
raise ValueError(f"Job {job_id} {name}: not found or still running.")
|
|
146
|
+
if time.time() - path.stat().st_mtime > JOB_TTL_SECONDS:
|
|
147
|
+
path.unlink(missing_ok=True)
|
|
148
|
+
raise ValueError(f"Job {job_id} {name}: expired.")
|
|
149
|
+
return path.read_text()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@mcp.resource("ansys://{job_id}/log")
|
|
153
|
+
def ansys_log(job_id: str) -> str:
|
|
154
|
+
return _read_artifact(job_id, "log.txt")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@mcp.resource("ansys://{job_id}/results")
|
|
158
|
+
def ansys_results(job_id: str) -> str:
|
|
159
|
+
return _read_artifact(job_id, "results.rst")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def main() -> None:
|
|
163
|
+
parser = argparse.ArgumentParser(
|
|
164
|
+
description="Public MCP mock of GKN's HPC for Ansys/FEA analysis"
|
|
165
|
+
)
|
|
166
|
+
parser.add_argument(
|
|
167
|
+
"--runs-dir",
|
|
168
|
+
type=Path,
|
|
169
|
+
default=None,
|
|
170
|
+
help=f"Where to store job artifacts (default: {DEFAULT_RUNS_DIR})",
|
|
171
|
+
)
|
|
172
|
+
args = parser.parse_args()
|
|
173
|
+
|
|
174
|
+
runs = args.runs_dir
|
|
175
|
+
if runs is None:
|
|
176
|
+
env = os.environ.get("MOCK_GKN_HPC_RUNS_DIR")
|
|
177
|
+
runs = Path(env) if env else DEFAULT_RUNS_DIR
|
|
178
|
+
|
|
179
|
+
global RUNS_DIR
|
|
180
|
+
RUNS_DIR = runs
|
|
181
|
+
|
|
182
|
+
mcp.run()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
if __name__ == "__main__":
|
|
186
|
+
main()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
ANSYS RESULTS FILE (.rst) - text mock
|
|
2
|
+
=====================================
|
|
3
|
+
Run date: {run_date}
|
|
4
|
+
Run time: {run_time}
|
|
5
|
+
Job ID: {job_id}
|
|
6
|
+
Solver: Sparse Direct
|
|
7
|
+
Analysis: Linear Static
|
|
8
|
+
|
|
9
|
+
Summary
|
|
10
|
+
-------
|
|
11
|
+
Nodes: 12847
|
|
12
|
+
Elements: 8432
|
|
13
|
+
DOF: 38541
|
|
14
|
+
Load steps: 1
|
|
15
|
+
Substeps: 1
|
|
16
|
+
|
|
17
|
+
Maximum results
|
|
18
|
+
---------------
|
|
19
|
+
von Mises stress: 187.4 MPa (node 4521)
|
|
20
|
+
Displacement: 0.842 mm (node 9183)
|
|
21
|
+
Reaction force: (-2104.3, 8.2, -45.1) N
|
|
22
|
+
|
|
23
|
+
Solver
|
|
24
|
+
------
|
|
25
|
+
Iterations: 1
|
|
26
|
+
Final residual: 4.21e-09
|
|
27
|
+
CPU time: 28.7 s
|
|
28
|
+
Elapsed time: 30.1 s
|