diff --git a/amorphouspy_api/src/amorphouspy_api/mcp_server.py b/amorphouspy_api/src/amorphouspy_api/mcp_server.py index 1c472ed4..2507437a 100644 --- a/amorphouspy_api/src/amorphouspy_api/mcp_server.py +++ b/amorphouspy_api/src/amorphouspy_api/mcp_server.py @@ -28,8 +28,9 @@ 1. `submit_job` — submit a simulation (returns job ID + URLs). 2. `get_job_status` — poll until status is "completed". 3. `get_job_results` — retrieve analysis data. -4. `search_jobs` — find existing results for similar compositions. -5. `list_glasses` / `lookup_glass` — browse available compositions. +4. `get_job_settings` — inspect the original submission parameters. +5. `search_jobs` — find existing results for similar compositions. +6. `list_glasses` / `lookup_glass` — browse available compositions. """ mcp = FastMCP( @@ -51,6 +52,7 @@ def register_tools() -> None: from amorphouspy_api.routers.jobs import ( cancel_job, get_job_results, + get_job_settings, get_job_status, get_single_result, search_jobs, @@ -62,6 +64,7 @@ def register_tools() -> None: search_jobs, get_job_status, cancel_job, + get_job_settings, get_job_results, get_single_result, list_glasses, diff --git a/amorphouspy_api/src/amorphouspy_api/models.py b/amorphouspy_api/src/amorphouspy_api/models.py index 54fcf190..c14f10f8 100644 --- a/amorphouspy_api/src/amorphouspy_api/models.py +++ b/amorphouspy_api/src/amorphouspy_api/models.py @@ -487,6 +487,16 @@ class JobStatusResponse(BaseModel): ) +class JobSettingsResponse(BaseModel): + """Response for ``GET /jobs/{id}/settings``.""" + + job_id: str + settings: dict = Field( + ..., + description="Original submission parameters (composition, potential, simulation, analyses, electrostatics, tags).", + ) + + class JobResultsResponse(BaseModel): """Response for ``GET /jobs/{id}/results``.""" diff --git a/amorphouspy_api/src/amorphouspy_api/routers/jobs.py b/amorphouspy_api/src/amorphouspy_api/routers/jobs.py index a0c3a451..5a907e8c 100644 --- a/amorphouspy_api/src/amorphouspy_api/routers/jobs.py +++ b/amorphouspy_api/src/amorphouspy_api/routers/jobs.py @@ -12,6 +12,7 @@ POST /jobs:search - search jobs across all statuses GET /jobs/{id} - poll job status POST /jobs/{id}:cancel - cancel a running job + GET /jobs/{id}/settings - original submission settings GET /jobs/{id}/results - all analysis results GET /jobs/{id}/results/{analysis} - single analysis result GET /jobs/{id}/structure - export quenched structure @@ -40,6 +41,7 @@ JobSearchMatch, JobSearchRequest, JobSearchResponse, + JobSettingsResponse, JobStatus, JobStatusResponse, JobSubmission, @@ -379,6 +381,23 @@ def update_tags(job_id: str, body: TagsUpdate) -> TagsResponse: return TagsResponse(job_id=job_id, tags=sorted(set(body.tags))) +@router.get("/{job_id}/settings", response_model=JobSettingsResponse) +def get_job_settings(job_id: str) -> JobSettingsResponse: + """Return the original submission settings for a job.""" + store = get_job_store() + job = store.get_job(job_id) + if not job: + raise HTTPException(status_code=404, detail="Job not found") + + if not job.request_data: + raise HTTPException(status_code=404, detail="No settings stored for this job") + + return JobSettingsResponse( + job_id=job.job_id, + settings=job.request_data, + ) + + @router.get("/{job_id}/results", response_model=JobResultsResponse) def get_job_results(job_id: str) -> JobResultsResponse: """All completed analysis results for a job.""" diff --git a/amorphouspy_api/src/tests/test_jobs.py b/amorphouspy_api/src/tests/test_jobs.py index d1c6c7b5..c6b0fbef 100644 --- a/amorphouspy_api/src/tests/test_jobs.py +++ b/amorphouspy_api/src/tests/test_jobs.py @@ -309,6 +309,42 @@ def test_get_results_no_data() -> None: assert resp.status_code == 404 +# --------------------------------------------------------------------------- +# GET /jobs/{id}/settings +# --------------------------------------------------------------------------- + + +def test_get_settings_completed() -> None: + _insert_completed_job("j-settings-1") + + resp = client.get("/jobs/j-settings-1/settings") + assert resp.status_code == 200 + data = resp.json() + assert data["job_id"] == "j-settings-1" + assert "composition" in data["settings"] + assert "potential" in data["settings"] + + +def test_get_settings_not_found() -> None: + resp = client.get("/jobs/nonexistent/settings") + assert resp.status_code == 404 + + +def test_get_settings_no_request_data() -> None: + store = get_job_store() + store.create_job( + Job( + job_id="j-no-settings", + request_hash="nosettings", + composition="SiO2 100", + potential="pmmcs", + status="completed", + ) + ) + resp = client.get("/jobs/j-no-settings/settings") + assert resp.status_code == 404 + + # --------------------------------------------------------------------------- # GET /jobs/{id}/results/{analysis} # ---------------------------------------------------------------------------