From a01daa3fa0295da10c52b37326740d80e586e350 Mon Sep 17 00:00:00 2001 From: Kris Wong Date: Fri, 7 Aug 2026 10:45:38 -0500 Subject: [PATCH 1/2] fix(platform): port upload_artifact.py to mcp 2.x SDK httpx2 client, 2-tuple streamable_http_client unpack, result.is_error; pin invocations to mcp[cli]>=2,<3 and dev group to mcp==2.0.0. --- plugins/platform/.claude-plugin/plugin.json | 2 +- .../platform/skills/upload-artifact/SKILL.md | 4 +- .../scripts/upload_artifact.py | 98 ++++++++-------- pyproject.toml | 2 +- uv.lock | 108 ++++++++++++------ 5 files changed, 120 insertions(+), 94 deletions(-) diff --git a/plugins/platform/.claude-plugin/plugin.json b/plugins/platform/.claude-plugin/plugin.json index cacbaba7..5983c15d 100644 --- a/plugins/platform/.claude-plugin/plugin.json +++ b/plugins/platform/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "platform", "description": "ClosedLoop Platform plugin", - "version": "1.1.3", + "version": "1.1.4", "author": { "name": "ClosedLoop", "email": "support@closedloop.ai" diff --git a/plugins/platform/skills/upload-artifact/SKILL.md b/plugins/platform/skills/upload-artifact/SKILL.md index 07b051f5..260d5695 100644 --- a/plugins/platform/skills/upload-artifact/SKILL.md +++ b/plugins/platform/skills/upload-artifact/SKILL.md @@ -48,7 +48,7 @@ otherwise rely on a `.env.local` file in the current working directory: Run the script with `--list-projects`: ```bash -uv run --with 'mcp[cli]' ${CLAUDE_SKILL_DIR}/scripts/upload_artifact.py \ +uv run --with 'mcp[cli]>=2,<3' ${CLAUDE_SKILL_DIR}/scripts/upload_artifact.py \ --url "$NEXT_PUBLIC_MCP_SERVER_URL" \ --api-key "$CLOSEDLOOP_API_KEY" \ --list-projects @@ -76,7 +76,7 @@ type — only ask for the title. ### Step 4a: Upload via Script ```bash -uv run --with 'mcp[cli]' ${CLAUDE_SKILL_DIR}/scripts/upload_artifact.py \ +uv run --with 'mcp[cli]>=2,<3' ${CLAUDE_SKILL_DIR}/scripts/upload_artifact.py \ --url "$NEXT_PUBLIC_MCP_SERVER_URL" \ --api-key "$CLOSEDLOOP_API_KEY" \ --file \ diff --git a/plugins/platform/skills/upload-artifact/scripts/upload_artifact.py b/plugins/platform/skills/upload-artifact/scripts/upload_artifact.py index 0982d63d..b44c0c68 100644 --- a/plugins/platform/skills/upload-artifact/scripts/upload_artifact.py +++ b/plugins/platform/skills/upload-artifact/scripts/upload_artifact.py @@ -15,23 +15,23 @@ export NEXT_PUBLIC_MCP_SERVER_URL=https://example.com/mcp # List available projects: - uv run --with 'mcp[cli]' scripts/upload_artifact.py \\ + uv run --with 'mcp[cli]>=2,<3' scripts/upload_artifact.py \\ --list-projects # Create document: - uv run --with 'mcp[cli]' scripts/upload_artifact.py \\ + uv run --with 'mcp[cli]>=2,<3' scripts/upload_artifact.py \\ --file /path/to/content.md \\ --title "My PRD" \\ --type PRD \\ --project-id # New version of existing document: - uv run --with 'mcp[cli]' scripts/upload_artifact.py \\ + uv run --with 'mcp[cli]>=2,<3' scripts/upload_artifact.py \\ --file /path/to/content.md \\ --artifact-id # Create + verify round-trip: - uv run --with 'mcp[cli]' scripts/upload_artifact.py \\ + uv run --with 'mcp[cli]>=2,<3' scripts/upload_artifact.py \\ --file /path/to/content.md \\ --title "My PRD" \\ --type PRD \\ @@ -45,9 +45,11 @@ import json import os import sys +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager from pathlib import Path -import httpx +import httpx2 from mcp import ClientSession from mcp.client.streamable_http import streamable_http_client @@ -71,13 +73,25 @@ class _Args(argparse.Namespace): _MCP_URL_ENV_VAR = "NEXT_PUBLIC_MCP_SERVER_URL" -def _build_http_client(api_key: str) -> httpx.AsyncClient: - return httpx.AsyncClient( +def _build_http_client(api_key: str) -> httpx2.AsyncClient: + return httpx2.AsyncClient( headers={"Authorization": f"Bearer {api_key}"}, - timeout=httpx.Timeout(120.0, read=300.0), + timeout=httpx2.Timeout(120.0, read=300.0), ) +@asynccontextmanager +async def _connect(args: _Args) -> AsyncIterator[ClientSession]: + """Open an initialized MCP session over Streamable HTTP.""" + async with _build_http_client(args.api_key) as http_client: + async with streamable_http_client( + args.url, http_client=http_client + ) as (read_stream, write_stream): + async with ClientSession(read_stream, write_stream) as session: + await session.initialize() + yield session + + def _extract_text(result) -> str: """Extract the first text content from an MCP tool result.""" for c in result.content or []: @@ -88,26 +102,15 @@ def _extract_text(result) -> str: async def list_projects(args: _Args) -> dict: """List all available projects.""" - http_client = _build_http_client(args.api_key) try: - async with http_client, streamable_http_client( - args.url, http_client=http_client - ) as (read_stream, write_stream, _): - async with ClientSession(read_stream, write_stream) as session: - await session.initialize() - result = await session.call_tool("list-projects", {}) - if result.isError: - return { - "error": "list-projects failed", - "details": [ - getattr(c, "text", str(c)) - for c in (result.content or []) - ], - } - text = _extract_text(result) - if not text: - return {"error": "Empty response from list-projects"} - return json.loads(text) + async with _connect(args) as session: + result = await session.call_tool("list-projects", {}) + if result.is_error: + return {"error": "list-projects failed", **_error_details(result)} + text = _extract_text(result) + if not text: + return {"error": "Empty response from list-projects"} + return json.loads(text) except (Exception, ExceptionGroup) as exc: return _format_exception(exc) @@ -126,7 +129,7 @@ async def _version_document( "create-document-version", {"documentId": document_id, "content": content}, ) - if result.isError: + if result.is_error: return {"error": "create-document-version failed", **_error_details(result)} text = _extract_text(result) parsed = json.loads(text) if text else {} @@ -159,7 +162,7 @@ async def _create_document( tool_args["workstreamId"] = args.workstream_id result = await session.call_tool("create-document", tool_args) - if result.isError: + if result.is_error: return {"error": "create-document failed", **_error_details(result)} text = _extract_text(result) if not text: @@ -187,25 +190,19 @@ async def upload(args: _Args) -> dict: content = file_path.read_text(encoding="utf-8") - http_client = _build_http_client(args.api_key) try: - async with http_client, streamable_http_client( - args.url, http_client=http_client - ) as (read_stream, write_stream, _): - async with ClientSession(read_stream, write_stream) as session: - await session.initialize() - - if args.artifact_id: - output = await _version_document(session, args.artifact_id, content) - else: - output = await _create_document(session, args, content) - - if "error" not in output and args.verify: - output["verify"] = await _verify_document( - session, output["artifact_id"], len(content) - ) + async with _connect(args) as session: + if args.artifact_id: + output = await _version_document(session, args.artifact_id, content) + else: + output = await _create_document(session, args, content) + + if "error" not in output and args.verify: + output["verify"] = await _verify_document( + session, output["artifact_id"], len(content) + ) - return output + return output except (Exception, ExceptionGroup) as exc: return _format_exception(exc) @@ -223,13 +220,8 @@ async def _verify_document( "contentMaxChars": fetch_max, }, ) - if result.isError: - return { - "error": "get-document failed", - "details": [ - getattr(c, "text", str(c)) for c in (result.content or []) - ], - } + if result.is_error: + return {"error": "get-document failed", **_error_details(result)} text = _extract_text(result) if not text: return {"error": "Empty response from get-document"} diff --git a/pyproject.toml b/pyproject.toml index b8494651..1d43bc0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ extraPaths = ["plugins/code/skills/plan-validate/scripts"] [dependency-groups] dev = [ "anthropic==0.92.0", - "mcp==1.27.0", + "mcp==2.0.0", "pyright==1.1.408", "pytest==9.0.3", "PyYAML==6.0.3", diff --git a/uv.lock b/uv.lock index 36198235..0718d195 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,10 @@ version = 1 revision = 3 requires-python = ">=3.13" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version < '3.14'", +] [[package]] name = "annotated-types" @@ -125,7 +129,7 @@ dev = [ [package.metadata.requires-dev] dev = [ { name = "anthropic", specifier = "==0.92.0" }, - { name = "mcp", specifier = "==1.27.0" }, + { name = "mcp", specifier = "==2.0.0" }, { name = "pyright", specifier = "==1.1.408" }, { name = "pytest", specifier = "==9.0.3" }, { name = "pyyaml", specifier = "==6.0.3" }, @@ -246,6 +250,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] +[[package]] +name = "httpcore2" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, + { name = "truststore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/a8/20ed1ed79cbc2ecdf5301c0968ab7c85547212e2a7bd126ddd2d986e206e/httpcore2-2.9.1.tar.gz", hash = "sha256:4d8acbf8b306f48c9d6046591fd5ba4037d1b1b1000d140fc2c3eab1e9a0c0e2", size = 67089, upload-time = "2026-07-24T09:21:03.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/fb/46c52b781975c335a2bcf1072c7bbc007cbdc8d674217f5ee1daba2c848b/httpcore2-2.9.1-py3-none-any.whl", hash = "sha256:6182472379e855fe4221246a2bb7ecede403bc61c6798062ae1787d051ccde26", size = 82809, upload-time = "2026-07-24T09:21:01.178Z" }, +] + [[package]] name = "httpx" version = "0.28.1" @@ -262,21 +279,27 @@ wheels = [ ] [[package]] -name = "httpx-sse" -version = "0.4.3" +name = "httpx2" +version = "2.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } +dependencies = [ + { name = "anyio" }, + { name = "httpcore2" }, + { name = "idna" }, + { name = "truststore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/14/38128fbafd7e0ed41d874df6c9a653d47c2d111cfe59e2b4ac95161b4abd/httpx2-2.9.1.tar.gz", hash = "sha256:1932a768737e3666291582833da748cc4e563c337cf96706fccc04fa6e58764a", size = 95458, upload-time = "2026-07-24T09:21:04.972Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, + { url = "https://files.pythonhosted.org/packages/13/b8/cfd91c4ab9134d386d48f0b6ac662ff3d4be6efdee59ee1c67ebc3c0487c/httpx2-2.9.1-py3-none-any.whl", hash = "sha256:1820fe14a9ab1107bfeff39259987429450b070ec0ff38cc87eb0d8c97fdc71a", size = 91191, upload-time = "2026-07-24T09:21:02.6Z" }, ] [[package]] name = "idna" -version = "3.14" +version = "3.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/b1/efac073e0c297ecf2fb33c346989a529d4e19164f1759102dee5953ee17e/idna-3.14.tar.gz", hash = "sha256:466d810d7a2cc1022bea9b037c39728d51ae7dad40d480fc9b7d7ecf98ba8ee3", size = 198272, upload-time = "2026-05-10T20:32:15.935Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/3c/3f62dee257eb3d6b2c1ef2a09d36d9793c7111156a73b5654d2c2305e5ce/idna-3.14-py3-none-any.whl", hash = "sha256:e677eaf072e290f7b725f9acf0b3a2bd55f9fd6f7c70abe5f0e34823d0accf69", size = 72184, upload-time = "2026-05-10T20:32:14.295Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, ] [[package]] @@ -371,15 +394,15 @@ wheels = [ [[package]] name = "mcp" -version = "1.27.0" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, - { name = "httpx" }, - { name = "httpx-sse" }, + { name = "httpx2" }, { name = "jsonschema" }, + { name = "mcp-types" }, + { name = "opentelemetry-api" }, { name = "pydantic" }, - { name = "pydantic-settings" }, { name = "pyjwt", extra = ["crypto"] }, { name = "python-multipart" }, { name = "pywin32", marker = "sys_platform == 'win32'" }, @@ -389,9 +412,22 @@ dependencies = [ { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/eb/c0cfc62075dc6e1ec1c64d352ae09ac051d9334311ed226f1f425312848a/mcp-1.27.0.tar.gz", hash = "sha256:d3dc35a7eec0d458c1da4976a48f982097ddaab87e278c5511d5a4a56e852b83", size = 607509, upload-time = "2026-04-02T14:48:08.88Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/33/32d4dff2c95bb5d897c3ef4c83649a08996b17b58f0a326d2495d4c81179/mcp-2.0.0.tar.gz", hash = "sha256:0f440e735c13ece8bb19bc62cf0b86f4313448432fbb77d35e14034f4e050728", size = 1662284, upload-time = "2026-07-28T13:45:32.346Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/46/f6b4ad632c67ef35209a66127e4bddc95759649dd595f71f13fba11bdf9a/mcp-1.27.0-py3-none-any.whl", hash = "sha256:5ce1fa81614958e267b21fb2aa34e0aea8e2c6ede60d52aba45fd47246b4d741", size = 215967, upload-time = "2026-04-02T14:48:07.24Z" }, + { url = "https://files.pythonhosted.org/packages/67/72/7d7897418912c1d12e87556630dfb7bf0eac71160e9bef8b447960804ee3/mcp-2.0.0-py3-none-any.whl", hash = "sha256:1cb4c75d2d2c7b8c1d756355e5d82a39f2822cc7f13e22a2051d7ca3592349d6", size = 349980, upload-time = "2026-07-28T13:45:28.853Z" }, +] + +[[package]] +name = "mcp-types" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/56/9b8e1c152f61f6c6b07c4b5896c88c7d0ae90bac6ee6306f852fcc5c1eb0/mcp_types-2.0.0.tar.gz", hash = "sha256:d7d939b9285c9961ae8866ba75ef85da34d12bafe276efbf4eb6a131786d8379", size = 66632, upload-time = "2026-07-28T13:45:33.804Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/4c/c78d78c3d52b0ac594ad7cc8ef5972adfe070e3597a8a4c6ce0cd39196ea/mcp_types-2.0.0-py3-none-any.whl", hash = "sha256:6b2de797ca2797f568b79529e1b25948e34de511bcc0bd82fef1039a6d1b8eb0", size = 69649, upload-time = "2026-07-28T13:45:30.713Z" }, ] [[package]] @@ -403,6 +439,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] +[[package]] +name = "opentelemetry-api" +version = "1.44.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/8b/aa9e2d8b8dfa7c946f7dec5d1f8f6ba8eca062f43509a06bdb5ce93d26c0/opentelemetry_api-1.44.0.tar.gz", hash = "sha256:67647e5e9566edcf421166fdf022b3537f818635daa852b289e34604dc6fb33a", size = 72406, upload-time = "2026-07-16T15:25:32.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/6f/a04e900f465ff3221ccc395522503e2d10e79fa21f2723c8e177aae1e0d1/opentelemetry_api-1.44.0-py3-none-any.whl", hash = "sha256:94b98c893a91b88657eaac1e3ba89618cdb85be6918196705354f34728b2cdef", size = 60018, upload-time = "2026-07-16T15:25:11.657Z" }, +] + [[package]] name = "packaging" version = "26.2" @@ -501,20 +549,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, ] -[[package]] -name = "pydantic-settings" -version = "2.14.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/07/60/1d1e59c9c90d54591469ada7d268251f71c24bdb765f1a8a832cee8c6653/pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa", size = 235551, upload-time = "2026-05-08T13:40:06.542Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de", size = 60964, upload-time = "2026-05-08T13:40:04.958Z" }, -] - [[package]] name = "pygments" version = "2.20.0" @@ -567,15 +601,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] -[[package]] -name = "python-dotenv" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, -] - [[package]] name = "python-multipart" version = "0.0.28" @@ -772,6 +797,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" }, ] +[[package]] +name = "truststore" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" From ee61712a16ff2e56ced821d51d5a2b7b9496e9be Mon Sep 17 00:00:00 2001 From: Kris Wong Date: Fri, 7 Aug 2026 10:46:17 -0500 Subject: [PATCH 2/2] docs(platform): changelog for upload-artifact mcp 2.x port --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69dac2dc..df86e399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to the claude-plugins project will be documented in this fil The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Entries are listed newest-first; each plugin section is treated as released when merged to `main`. +### platform v1.1.4 + +#### Fixed +- **`upload-artifact` script mode now works against MCP Python SDK 2.x.** `upload_artifact.py` unpacked three values from `streamable_http_client`, read `result.isError`, and built its authenticated client with `httpx` — all 1.x-era shapes. Under SDK 2.x the transport yields a two-value `(read_stream, write_stream)` tuple, `CallToolResult` exposes `is_error`, and the transport expects an `httpx2.AsyncClient`; because the documented `uv run --with 'mcp[cli]'` invocation was unpinned it resolved to 2.x, so every run failed at connect time. The script now targets the 2.x API, and both the module docstring and `SKILL.md` pin the invocation to `mcp[cli]>=2,<3`. Connection setup shared by `--list-projects` and upload is consolidated into a single `_connect` async context manager, and the `get-document` error path reuses the existing `_error_details` helper instead of rebuilding the detail list. The repo dev dependency group moves to `mcp==2.0.0` so type checking runs against the same SDK the script requires. + ### code-review v3.7.0 #### Changed