From a72d00e51a815f10c9e168d2f2a819a28d989b47 Mon Sep 17 00:00:00 2001 From: caballeto Date: Wed, 22 Apr 2026 11:40:37 +0200 Subject: [PATCH 1/2] feat: add reorder_status_page_layout MCP tool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a top-level `reorder_status_page_layout` tool that wraps `StatusPages.reorder_layout(...)` from `devhelm 0.2.1`. This closes the parity gap on `status-pages.reorder-layout` between sdk-js (which has had `reorderLayout` for a while) and the MCP surface, and lets agents drive full drag-and-drop layout edits — moving groups + ungrouped components in one call, with optional within-group reordering via `groupOrders`. Tool description distinguishes it from the existing `reorder_status_page_components` tool: use `reorder_status_page_layout` when the top-level layout (groups + ungrouped components) changes; use `reorder_status_page_components` when only one group's internal order changes. Also bumps `devhelm` floor from `>=0.2.0` to `>=0.2.1` (where the new `StatusPages.reorder_layout` lives, and where the bogus `Incidents.delete()` was removed). CI will fail on `uv sync` until `devhelm 0.2.1` is published to PyPI; this is the same chicken-and-egg as the previous 0.1.x → 0.2.0 bump. Verified locally with a temporary `[tool.uv.sources]` override pointing at `../sdk-python` (stripped before commit): - `pytest tests/` — 57 passed - `mypy src/` — clean (18 files) - `ruff format` / `ruff check` — clean - `make test-surface SURFACE=mcp_server` against test API — 93 passed Made-with: Cursor --- pyproject.toml | 10 +++++----- src/devhelm_mcp/tools/status_pages.py | 24 ++++++++++++++++++++++++ tests/test_tools.py | 2 ++ 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cb05cee..1455af9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,10 +19,10 @@ classifiers = [ ] dependencies = [ - # Floor bumped to 0.1.3 because mcp-server depends on the strict error - # taxonomy (DevhelmValidationError / DevhelmApiError / DevhelmTransportError) - # and `extra='forbid'` response validation introduced in that release. - "devhelm>=0.2.0", + # Floor bumped to 0.2.1 for `StatusPages.reorder_layout` and the + # `Incidents.delete` removal (which the `reorder_status_page_layout` + # tool below depends on, and which the parity check requires). + "devhelm>=0.2.1", "fastmcp>=2.0.0", ] @@ -64,4 +64,4 @@ dev = [ "pytest>=8.0", "ruff>=0.8", "twine>=5.0", -] +] \ No newline at end of file diff --git a/src/devhelm_mcp/tools/status_pages.py b/src/devhelm_mcp/tools/status_pages.py index 8b641df..8dc4ef3 100644 --- a/src/devhelm_mcp/tools/status_pages.py +++ b/src/devhelm_mcp/tools/status_pages.py @@ -13,6 +13,7 @@ CreateStatusPageIncidentUpdateRequest, CreateStatusPageRequest, ReorderComponentsRequest, + ReorderPageLayoutRequest, UpdateStatusPageComponentGroupRequest, UpdateStatusPageComponentRequest, UpdateStatusPageIncidentRequest, @@ -85,6 +86,29 @@ def delete_status_page(api_token: str, page_id: str) -> str: except DevhelmError as e: return format_error(e) + @mcp.tool() + def reorder_status_page_layout( + api_token: str, page_id: str, body: ReorderPageLayoutRequest + ) -> str: + """Batch-reorder a status page's full layout. + + Required: sections — top-level layout in their new order, where each + entry is either {kind:"component", componentId} or + {kind:"group", groupId}. Use ``groupOrders`` (optional) to also + reorder components within specific groups; only include groups whose + internal order changed. The full top-level set must be provided — + partial reorders are rejected by the API. + + Use this for "drag-and-drop" layout edits that touch both groups and + ungrouped components. To reorder components within a single group + only, prefer ``reorder_status_page_components``. + """ + try: + _sp(api_token).reorder_layout(page_id, as_payload(body)) + return "Status page layout reordered successfully." + except DevhelmError as e: + return format_error(e) + # ── Components ──────────────────────────────────────────────────────── @mcp.tool() diff --git a/tests/test_tools.py b/tests/test_tools.py index 57bc408..11d70dd 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -87,6 +87,7 @@ "create_status_page", "update_status_page", "delete_status_page", + "reorder_status_page_layout", "list_status_page_components", "create_status_page_component", "update_status_page_component", @@ -157,6 +158,7 @@ def test_tool_count(self, registered_tools: RegisteredTools) -> None: "create_status_page", "update_status_page", "delete_status_page", + "reorder_status_page_layout", ] STATUS_PAGE_COMPONENT_TOOLS = [ From 9df6a60db4a61f567eedfbef3832a1271ea542a4 Mon Sep 17 00:00:00 2001 From: caballeto Date: Wed, 22 Apr 2026 12:03:51 +0200 Subject: [PATCH 2/2] =?UTF-8?q?chore(deps):=20bump=20devhelm=200.2.0=20?= =?UTF-8?q?=E2=86=92=200.2.1=20in=20lockfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `pyproject.toml` already pins `devhelm>=0.2.1` (added in this PR for the `reorder_status_page_layout` tool), but `uv.lock` still resolved to 0.2.0 from when the lockfile was last regenerated — before sdk-python 0.2.1 hit PyPI. CI's `uv sync` failed because the cached PyPI index in `setup-uv@v4` plus the stale lock left it unable to satisfy `>=0.2.1`. Regenerated locally with `uv cache clean devhelm && uv sync --upgrade-package devhelm --refresh`. Lock now pins `devhelm==0.2.1` from `https://pypi.org/simple`. Verified pytest (57 passed) and `ruff check` are still green against the upgraded SDK. Made-with: Cursor --- uv.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/uv.lock b/uv.lock index 1c85b17..dc1b544 100644 --- a/uv.lock +++ b/uv.lock @@ -388,20 +388,20 @@ wheels = [ [[package]] name = "devhelm" -version = "0.2.0" +version = "0.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, { name = "pydantic", extra = ["email"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/f2/5af595f2eaa3696874f5d93aae2a7efd3d06abf36d38f56cfb1a5824f632/devhelm-0.2.0.tar.gz", hash = "sha256:3160c5f6098756bf80a49cc2489ed809797790f209214376091c8e560532fba8", size = 223354, upload-time = "2026-04-22T08:30:19.004Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/9b/15f9e05f6b6ac44ac5d17c3e5bc37791451c7f98758641ac1aa308f94648/devhelm-0.2.1.tar.gz", hash = "sha256:5188d99ee01e43139cfc96b46174f35786e23fb077f15e153408068eb182e37f", size = 223541, upload-time = "2026-04-22T09:58:42.574Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/73/fed4d13b648b5c2bdc06a944bcb7d1787b40778a8ff98fd05c41b88212f7/devhelm-0.2.0-py3-none-any.whl", hash = "sha256:655772d06931fc65a823230346d581a6c93be23c5ee9158039f3e9ac4f30c9bb", size = 67575, upload-time = "2026-04-22T08:30:17.808Z" }, + { url = "https://files.pythonhosted.org/packages/73/58/2b3146c85eef0ef6c129a7a0b41a9a23bf21a03590693ce0acfbd82bc0c3/devhelm-0.2.1-py3-none-any.whl", hash = "sha256:83ef1a5b1e95742fc60bd0c8bf77287f3dff55565b60f7b397ca4a3dace5d882", size = 67714, upload-time = "2026-04-22T09:58:41.286Z" }, ] [[package]] name = "devhelm-mcp-server" -version = "0.1.1" +version = "0.2.0" source = { editable = "." } dependencies = [ { name = "devhelm" }, @@ -419,7 +419,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "devhelm", specifier = ">=0.2.0" }, + { name = "devhelm", specifier = ">=0.2.1" }, { name = "fastmcp", specifier = ">=2.0.0" }, ]