Skip to content
Open
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
101 changes: 100 additions & 1 deletion plane_mcp/tools/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Any

from fastmcp import FastMCP
from plane.models.pages import CreatePage, Page
from plane.models.pages import CreatePage, Page, UpdatePage
from plane.models.work_item_pages import CreateWorkItemPage, WorkItemPage

from plane_mcp.client import get_plane_client_context
Expand Down Expand Up @@ -199,3 +199,102 @@ def create_page(
workspace_slug=workspace_slug,
data=data,
)

@mcp.tool()
def update_page(
page_id: str,
project_id: str | None = None,
name: str | None = None,
description_html: str | None = None,
access: int | None = None,
color: str | None = None,
is_locked: bool | None = None,
archived_at: str | None = None,
view_props: dict[str, Any] | None = None,
logo_props: dict[str, Any] | None = None,
external_id: str | None = None,
external_source: str | None = None,
) -> Page:
"""
Update a page.

Updates a project page if project_id is given, otherwise a
workspace-level page. Only the fields provided are changed;
omitted fields are left as-is.

Args:
page_id: UUID of the page to update
project_id: UUID of the project. Omit to update a workspace page.
name: Page name
description_html: Page content in HTML format
access: Access level for the page (integer)
color: Page color
is_locked: Whether the page is locked
archived_at: Archive timestamp (ISO 8601 format)
view_props: View properties dictionary
logo_props: Logo properties dictionary
external_id: External system identifier
external_source: External system source name

Returns:
Updated Page object
"""
client, workspace_slug = get_plane_client_context()

data = UpdatePage(
name=name,
description_html=description_html,
access=access,
color=color,
is_locked=is_locked,
archived_at=archived_at,
view_props=view_props,
logo_props=logo_props,
external_id=external_id,
external_source=external_source,
)

if project_id is not None:
return client.pages.update_project_page(
workspace_slug=workspace_slug,
project_id=project_id,
page_id=page_id,
data=data,
)
return client.pages.update_workspace_page(
workspace_slug=workspace_slug,
page_id=page_id,
data=data,
)
Comment on lines +257 to +268

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files matching pages.py / dependency files =="
git ls-files | rg '(^|/)pages\.py$|(^|/)(requirements|pyproject|poetry|setup|Pipfile|uv\.lock|poetry\.lock|max_length)' || true

echo
echo "== target slice =="
sed -n '220,290p' plane_mcp/tools/pages.py 2>/dev/null || true

echo
echo "== search for SDK methods in repo =="
rg -n "update_(project|workspace)_page|pages:|makeplane|plane-python-sdk|plane_python_sdk|workspace_page|project_page" -S . || true

echo
echo "== dependency declarations =="
for f in pyproject.toml requirements.txt requirements-base.txt requirements-dev.txt pyproject.lock uv.lock poetry.lock Pipfile Pipfile.lock; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    rg -n "makeplane|plane-python-sdk|plane_python_sdk|Plane" "$f" -S || true
  fi
done

echo
echo "== inspect likely dependency files sizes =="
wc -l pyproject.toml requirements*.txt uv.lock poetry.lock 2>/dev/null | sed -n '1,80p' || true

Repository: makeplane/plane-mcp-server

Length of output: 5975


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== pyproject.toml =="
cat -n pyproject.toml

echo
echo "== uv.lock dependency references (focused) =="
rg -n 'name = "makeplane|makeplane|plane|pages|update_(project|workspace)_page' uv.lock pyproject.toml -S || true

echo
echo "== inspect dependency section in pyproject =="
sed -n '1,80p' pyproject.toml

echo
echo "== inspect relevant uv.lock packages =="
python3 - <<'PY'
from pathlib import Path
p=Path("uv.lock")
text=p.read_text()
for marker in ['[[package]]\nname = "makeplane"\n', '[[package]]\nname = "plane-python-sdk"\n', '[[package]]\nname = "plane-python-sdk"']:
    idx=text.find(marker)
    print("marker", repr(marker), "idx", idx)
PY

echo
echo "== remote SDK pages.py =="
curl -sS --fail https://raw.githubusercontent.com/makeplane/plane-python-sdk/main/plane/api/pages.py | sed -n '1,260p'

echo
echo "== remote SDK Pages class methods summary =="
python3 - <<'PY'
import re, urllib.request
url="https://raw.githubusercontent.com/makeplane/plane-python-sdk/main/plane/api/pages.py"
try:
    data=urllib.request.urlopen(url, timeout=20).read().decode()
except Exception as e:
    print("FETCH_ERROR", repr(type(e).__name__), str(e))
    raise SystemExit(0)
methods=re.findall(r'^\s{4}def ([a-z_][a-z_0-9]*)\(', data, re.M)
print(methods)
print()
for m in methods:
    if "update" in m:
        print(m)
PY

Repository: makeplane/plane-mcp-server

Length of output: 9718


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json, tarfile, sys, urllib.request, tempfile, pathlib
for version in ["0.2.16", "0.2.17", "0.2.18", "0.2.19", "0.2.20", "0.2.21"]:
    url = f"https://files.pythonhosted.org/packages/source/p/plane-sdk/plane_sdk-{version}.tar.gz"
    print(f"--- plane-sdk {version} ---")
    try:
        data = urllib.request.urlopen(url, timeout=20).read()
        with tarfile.open(fileobj=sys.stdout.buffer, mode="r:gz") as outer:
            pass
        tar = tarfile.open(fileobj=sys.stdout.buffer, mode="r:gz")
    except Exception as e:
        print("FETCH_ERROR", type(e).__name__, str(e))
        continue
    pkg = pathlib.Path(f"plane_sdk-{version}/plane/api/pages.py")
    for member in tar.getmembers():
        if not member.isfile():
            continue
        contents = tar.extractfile(member)
        if contents is None:
            continue
        text = contents.read().decode("utf-8", "replace")
        names = []
        for line in text.splitlines():
            if line.strip().startswith("def "):
                names.append(line.strip().split("(", 1)[0].replace("def ", "", 1))
        if "Pages" in names or any("update" in n for n in names) or member.name.endswith("pages.py"):
            update = [n for n in names if "update" in n]
            print("tar path", member.name, "update methods", update)
PY

Repository: makeplane/plane-mcp-server

Length of output: 1250


🌐 Web query:

PyPI plane-sdk 0.2.20 plane.api.pages Pages update_project_page update_workspace_page

💡 Result:

In the plane-sdk (v0.2.20), the Page management functionality is available through the client.pages resource [1]. While the SDK is organized to follow standard CRUD patterns (list, create, retrieve, update, delete) [2][3], current developer documentation and resource availability for the specific methods update_workspace_page and update_project_page in the Python SDK are limited. As of recent updates to the Plane API (v1), support for Page CRUD operations (including update) has been added to the underlying REST API [4][5]. In the plane-sdk, you typically interact with resources via the PlaneClient [6][3]. Based on the established patterns for other resources in this SDK, these update operations, if exposed, would likely follow this signature structure: client.pages.update_workspace_page(workspace_slug, page_id, data) client.pages.update_project_page(workspace_slug, project_id, page_id, data) Please check the official Plane Python SDK repository directly for the most current implementation, as the SDK is frequently updated to reflect new v1 API endpoints [7][3]. If these specific methods are not yet explicitly defined in your current installed version, you may need to utilize the underlying client to perform the PATCH request directly against the v1 API endpoints documented in the Plane developer portal [4][5][8].

Citations:


🌐 Web query:

"def update_workspace_page" "makeplane.plane-python-sdk"

💡 Result:

The plane-python-sdk does not currently provide a native method named update_workspace_page [1][2][3]. While the Plane API has recently expanded to include CRUD operations for pages [4], the current version of the official Python SDK primarily exposes methods such as list_workspace_pages, list_project_pages, retrieve_workspace_page, and retrieve_project_page [1][2][3]. If you need to update a workspace page programmatically, you may need to use the underlying API directly via the PATCH method at the endpoint /api/v1/workspaces/{slug}/pages/{page_id}/ [4][5], utilizing a standard HTTP client such as requests or httpx along with your API key or OAuth token for authentication [5]. Alternatively, check the repository for the most recent updates, as the SDK is actively maintained and methods are added to match API capabilities [3][6].

Citations:


Ship an SDK version that implements these methods.

plane_mcp/tools/pages.py:update_page() calls client.pages.update_project_page() and update_workspace_page(), but the pinned plane-sdk==0.2.20 lock is for 0.2.16 and the SDK pages resource only exposes list/retrieve/create/delete for pages. That makes update_page fail immediately at runtime on installed environments. Pin a compatible SDK version that includes both methods, or implement the update calls through the existing SDK/client API before registering this tool.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plane_mcp/tools/pages.py` around lines 257 - 268, Update the dependency pin
used by update_page so the installed Plane SDK exposes both
pages.update_project_page and pages.update_workspace_page, ensuring the lock and
declared version resolve to that compatible release. If no compatible SDK
release exists, replace these calls with the supported existing client API
before registering update_page.


@mcp.tool()
def delete_page(
page_id: str,
project_id: str | None = None,
) -> None:
"""
Delete a page.

Permanently deletes a project page if project_id is given,
otherwise a workspace-level page. This action cannot be undone.

Args:
page_id: UUID of the page to delete
project_id: UUID of the project. Omit to delete a workspace page.

Returns:
None
"""
Comment thread
coderabbitai[bot] marked this conversation as resolved.
client, workspace_slug = get_plane_client_context()

if project_id is not None:
client.pages.delete_project_page(
workspace_slug=workspace_slug,
project_id=project_id,
page_id=page_id,
)
else:
client.pages.delete_workspace_page(
workspace_slug=workspace_slug,
page_id=page_id,
)