From 15c8b0c64da319f4eb68fbd064cd68ebe92be64d Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 12:22:23 -0400 Subject: [PATCH 01/65] Port fetch, git and time to the MCP Python SDK v2 Move the three Python servers from mcp 1.x to mcp>=2.2,<3 and drop the mcp>=1.29.0,<2 pin. Each stays on the low-level Server, now built with on_* constructor handlers instead of decorators, and returns ListToolsResult / ListPromptsResult / CallToolResult explicitly. SDK v2's low-level server no longer validates tool arguments against inputSchema or folds a handler exception into an isError result. Each server's call_tool now does both itself, with v1's messages, so tool results on the wire are unchanged. jsonschema becomes a direct dependency for that. McpError(ErrorData(...)) becomes MCPError(code=..., message=...), and Tool / ToolAnnotations fields move to snake_case. Tests: only import and type changes, plus wire() dumping with exclude_unset (v2 result models default fields the 2025-11-25 wire does not carry). One assertion changes: v2 omits the empty "experimental": {} capability, in git and time. Closes #4851 Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/fetch/README.md | 2 +- src/fetch/pyproject.toml | 3 +- src/fetch/src/mcp_server_fetch/server.py | 190 ++++++++------- src/fetch/tests/harness.py | 2 +- src/fetch/tests/test_protocol.py | 21 +- src/fetch/uv.lock | 137 +++++++---- src/git/README.md | 2 +- src/git/pyproject.toml | 3 +- src/git/src/mcp_server_git/server.py | 296 +++++++++++++---------- src/git/tests/conftest.py | 7 +- src/git/tests/test_entrypoints.py | 2 +- src/git/tests/test_protocol.py | 16 +- src/git/uv.lock | 153 ++++++------ src/time/README.md | 2 +- src/time/pyproject.toml | 3 +- src/time/src/mcp_server_time/server.py | 158 +++++++----- src/time/tests/test_protocol.py | 23 +- src/time/tests/test_server.py | 6 +- src/time/uv.lock | 152 ++++++------ 19 files changed, 666 insertions(+), 512 deletions(-) diff --git a/src/fetch/README.md b/src/fetch/README.md index 5f64d7952b..2441bd2b89 100644 --- a/src/fetch/README.md +++ b/src/fetch/README.md @@ -6,7 +6,7 @@ A Model Context Protocol server that provides web content fetching capabilities. Source: https://github.com/modelcontextprotocol/servers/tree/main/src/fetch -Requires MCP Python SDK 1.x (`mcp>=1.29.0,<2`). SDK 2.0 renamed APIs this server uses. The port to v2 is in progress. +Requires MCP Python SDK 2.x (`mcp>=2.2,<3`). > [!CAUTION] > This server can access local/internal IP addresses and may represent a security risk. Exercise caution when using this MCP server to ensure this does not expose any sensitive data. diff --git a/src/fetch/pyproject.toml b/src/fetch/pyproject.toml index a2f289ef77..0666d3b768 100644 --- a/src/fetch/pyproject.toml +++ b/src/fetch/pyproject.toml @@ -18,7 +18,8 @@ classifiers = [ dependencies = [ "httpx[socks]>=0.27", "markdownify>=0.13.1", - "mcp>=1.29.0,<2", + "jsonschema>=4.20.0", + "mcp>=2.2,<3", "protego>=0.3.1", "pydantic>=2.0.0", "readabilipy>=0.2.0", diff --git a/src/fetch/src/mcp_server_fetch/server.py b/src/fetch/src/mcp_server_fetch/server.py index 6b0073f478..2adb74dcaf 100644 --- a/src/fetch/src/mcp_server_fetch/server.py +++ b/src/fetch/src/mcp_server_fetch/server.py @@ -2,19 +2,26 @@ import ipaddress import os import socket -from typing import Annotated, Tuple +from typing import Annotated, Any, Tuple from importlib.metadata import version from urllib.parse import urlparse, urlunparse import httpx +import jsonschema import markdownify import readabilipy.simple_json -from mcp.shared.exceptions import McpError -from mcp.server import Server +from mcp.shared.exceptions import MCPError +from mcp.server import Server, ServerRequestContext from mcp.server.stdio import stdio_server from mcp.types import ( - ErrorData, + CallToolRequestParams, + CallToolResult, + ContentBlock, + GetPromptRequestParams, GetPromptResult, + ListPromptsResult, + ListToolsResult, + PaginatedRequestParams, Prompt, PromptArgument, PromptMessage, @@ -132,13 +139,11 @@ async def _refuse_private_destination(request: httpx.Request) -> None: return for address in addresses: if not _is_public_address(address): - raise McpError( - ErrorData( - code=INVALID_PARAMS, - message=f"Refused to fetch {request.url}: {host} resolves to {address}, " - "which is not a public address. Start the server with " - "--allow-private-ips to allow private, loopback and link-local addresses.", - ) + raise MCPError( + code=INVALID_PARAMS, + message=f"Refused to fetch {request.url}: {host} resolves to {address}, " + "which is not a public address. Start the server with " + "--allow-private-ips to allow private, loopback and link-local addresses.", ) @@ -227,13 +232,11 @@ def make_client(proxy_url: str | None) -> httpx.AsyncClient: try: return httpx.AsyncClient(proxy=proxy_url) except (ValueError, ImportError) as e: - raise McpError( - ErrorData( - code=INTERNAL_ERROR, - message=f"Failed to set up the HTTP client, check the proxy " - f"configuration (--proxy-url or the HTTP_PROXY, HTTPS_PROXY and " - f"ALL_PROXY environment variables): {e}", - ) + raise MCPError( + code=INTERNAL_ERROR, + message=f"Failed to set up the HTTP client, check the proxy " + f"configuration (--proxy-url or the HTTP_PROXY, HTTPS_PROXY and " + f"ALL_PROXY environment variables): {e}", ) @@ -245,7 +248,7 @@ async def check_may_autonomously_fetch_url( ) -> None: """ Check if the URL can be fetched by the user agent according to the robots.txt file. - Raises a McpError if not. + Raises an MCPError if not. """ from httpx import HTTPError @@ -261,18 +264,14 @@ async def check_may_autonomously_fetch_url( headers={"User-Agent": user_agent}, ) except HTTPError: - raise McpError( - ErrorData( - code=INTERNAL_ERROR, - message=f"Failed to fetch robots.txt {robot_txt_url} due to a connection issue", - ) + raise MCPError( + code=INTERNAL_ERROR, + message=f"Failed to fetch robots.txt {robot_txt_url} due to a connection issue", ) if response.status_code in (401, 403): - raise McpError( - ErrorData( - code=INTERNAL_ERROR, - message=f"When fetching robots.txt ({robot_txt_url}), received status {response.status_code} so assuming that autonomous fetching is not allowed, the user can try manually fetching by using the fetch prompt", - ) + raise MCPError( + code=INTERNAL_ERROR, + message=f"When fetching robots.txt ({robot_txt_url}), received status {response.status_code} so assuming that autonomous fetching is not allowed, the user can try manually fetching by using the fetch prompt", ) elif 400 <= response.status_code < 500: return @@ -282,16 +281,14 @@ async def check_may_autonomously_fetch_url( ) robot_parser = Protego.parse(processed_robot_txt) if not robot_parser.can_fetch(str(url), user_agent): - raise McpError( - ErrorData( - code=INTERNAL_ERROR, - message=f"The sites robots.txt ({robot_txt_url}), specifies that autonomous fetching of this page is not allowed, " - f"{user_agent}\n" - f"{url}" - f"\n{robot_txt}\n\n" - f"The assistant must let the user know that it failed to view the page. The assistant may provide further guidance based on the above information.\n" - f"The assistant can tell the user that they can try manually fetching the page by using the fetch prompt within their UI.", - ) + raise MCPError( + code=INTERNAL_ERROR, + message=f"The sites robots.txt ({robot_txt_url}), specifies that autonomous fetching of this page is not allowed, " + f"{user_agent}\n" + f"{url}" + f"\n{robot_txt}\n\n" + f"The assistant must let the user know that it failed to view the page. The assistant may provide further guidance based on the above information.\n" + f"The assistant can tell the user that they can try manually fetching the page by using the fetch prompt within their UI.", ) @@ -318,15 +315,11 @@ async def fetch_url( timeout=30, ) except HTTPError as e: - raise McpError( - ErrorData(code=INTERNAL_ERROR, message=f"Failed to fetch {url}: {e!r}") - ) + raise MCPError(code=INTERNAL_ERROR, message=f"Failed to fetch {url}: {e!r}") if response.status_code >= 400: - raise McpError( - ErrorData( - code=INTERNAL_ERROR, - message=f"Failed to fetch {url} - status code {response.status_code}", - ) + raise MCPError( + code=INTERNAL_ERROR, + message=f"Failed to fetch {url} - status code {response.status_code}", ) page_raw = response.text @@ -378,6 +371,13 @@ class Fetch(BaseModel): ] +def tool_error(message: str) -> CallToolResult: + """A tool call that failed, as an `isError` result the model can read.""" + return CallToolResult( + content=[TextContent(type="text", text=message)], is_error=True + ) + + async def serve( custom_user_agent: str | None = None, ignore_robots_txt: bool = False, @@ -396,44 +396,46 @@ async def serve( if proxy_url: proxy_url = normalize_proxy_url(proxy_url) normalize_proxy_env() - server = Server("mcp-fetch", version=SERVER_VERSION) user_agent_autonomous = custom_user_agent or DEFAULT_USER_AGENT_AUTONOMOUS user_agent_manual = custom_user_agent or DEFAULT_USER_AGENT_MANUAL - @server.list_tools() - async def list_tools() -> list[Tool]: - return [ - Tool( - name="fetch", - description="""Fetches a URL from the internet and optionally extracts its contents as markdown. + tool = Tool( + name="fetch", + description="""Fetches a URL from the internet and optionally extracts its contents as markdown. Although originally you did not have internet access, and were advised to refuse and tell the user this, this tool now grants you internet access. Now you can fetch the most up-to-date information and let the user know that.""", - inputSchema=Fetch.model_json_schema(), - ) - ] - - @server.list_prompts() - async def list_prompts() -> list[Prompt]: - return [ - Prompt( - name="fetch", - description="Fetch a URL and extract its contents as markdown", - arguments=[ - PromptArgument( - name="url", description="URL to fetch", required=True - ) - ], - ) - ] + input_schema=Fetch.model_json_schema(), + ) + + async def list_tools( + ctx: ServerRequestContext, params: PaginatedRequestParams | None + ) -> ListToolsResult: + return ListToolsResult(tools=[tool]) + + async def list_prompts( + ctx: ServerRequestContext, params: PaginatedRequestParams | None + ) -> ListPromptsResult: + return ListPromptsResult( + prompts=[ + Prompt( + name="fetch", + description="Fetch a URL and extract its contents as markdown", + arguments=[ + PromptArgument( + name="url", description="URL to fetch", required=True + ) + ], + ) + ] + ) - @server.call_tool() - async def call_tool(name, arguments: dict) -> list[TextContent]: + async def run_tool(name: str, arguments: dict[str, Any]) -> list[ContentBlock]: if name != "fetch": raise ValueError(f"Unknown tool: {name}") try: args = Fetch(**arguments) except ValueError as e: - raise McpError(ErrorData(code=INVALID_PARAMS, message=str(e))) + raise MCPError(code=INVALID_PARAMS, message=str(e)) url = str(args.url) @@ -468,14 +470,31 @@ async def call_tool(name, arguments: dict) -> list[TextContent]: content += f"\n\nContent truncated. Call the fetch tool with a start_index of {next_start} to get more content." return [TextContent(type="text", text=f"{prefix}Contents of {url}:\n{content}")] - @server.get_prompt() - async def get_prompt(name: str, arguments: dict | None) -> GetPromptResult: + async def call_tool( + ctx: ServerRequestContext, params: CallToolRequestParams + ) -> CallToolResult: + # SDK v2's low-level server neither validates arguments nor turns a + # handler exception into an isError result, both of which SDK v1 did. + # Both are done here, with v1's messages, so the wire is unchanged. + arguments = params.arguments or {} + if params.name == tool.name: + try: + jsonschema.validate(instance=arguments, schema=tool.input_schema) + except jsonschema.ValidationError as e: + return tool_error(f"Input validation error: {e.message}") + try: + return CallToolResult(content=await run_tool(params.name, arguments)) + except Exception as e: + return tool_error(str(e)) + + async def get_prompt( + ctx: ServerRequestContext, params: GetPromptRequestParams + ) -> GetPromptResult: + name, arguments = params.name, params.arguments if name != "fetch": - raise McpError( - ErrorData(code=INVALID_PARAMS, message=f"Unknown prompt: {name}") - ) + raise MCPError(code=INVALID_PARAMS, message=f"Unknown prompt: {name}") if not arguments or "url" not in arguments: - raise McpError(ErrorData(code=INVALID_PARAMS, message="URL is required")) + raise MCPError(code=INVALID_PARAMS, message="URL is required") url = arguments["url"] @@ -487,7 +506,7 @@ async def get_prompt(name: str, arguments: dict | None) -> GetPromptResult: allow_private_ips=allow_private_ips, ) # TODO: after SDK bug is addressed, don't catch the exception - except McpError as e: + except MCPError as e: return GetPromptResult( description=f"Failed to fetch {url}", messages=[ @@ -506,6 +525,15 @@ async def get_prompt(name: str, arguments: dict | None) -> GetPromptResult: ], ) + server = Server( + "mcp-fetch", + version=SERVER_VERSION, + on_list_tools=list_tools, + on_list_prompts=list_prompts, + on_call_tool=call_tool, + on_get_prompt=get_prompt, + ) + options = server.create_initialization_options() async with stdio_server() as (read_stream, write_stream): await server.run(read_stream, write_stream, options, raise_exceptions=False) diff --git a/src/fetch/tests/harness.py b/src/fetch/tests/harness.py index 6c521769b8..650faa34d1 100644 --- a/src/fetch/tests/harness.py +++ b/src/fetch/tests/harness.py @@ -4,7 +4,7 @@ - ``connect(...)`` runs the real ``serve()`` in-process and yields an initialized ``ClientSession`` linked to it over in-memory streams, so tests - go through the SDK's validation and error mapping exactly as a client would. + go through the server's validation and error mapping exactly as a client would. - ``web`` replaces ``httpx.AsyncClient`` with one backed by an ``httpx.MockTransport``, so no test touches the network. It records every request and the keyword arguments each client was constructed with. It also diff --git a/src/fetch/tests/test_protocol.py b/src/fetch/tests/test_protocol.py index deb5d9099f..b24f739025 100644 --- a/src/fetch/tests/test_protocol.py +++ b/src/fetch/tests/test_protocol.py @@ -22,7 +22,7 @@ import httpx import pytest from mcp import ClientSession -from mcp.shared.exceptions import McpError +from mcp.shared.exceptions import MCPError from mcp.types import INVALID_PARAMS, CallToolResult from mcp_server_fetch.server import ( @@ -52,7 +52,12 @@ def wire(model: Any) -> dict[str, Any]: - return model.model_dump(by_alias=True, mode="json", exclude_none=True) + # exclude_unset: SDK v2 result models default fields the 2025-11-25 wire + # does not carry (resultType, ttlMs, cacheScope), so only what the server + # actually sent is compared. + return model.model_dump( + by_alias=True, mode="json", exclude_none=True, exclude_unset=True + ) def text_of(result: CallToolResult) -> str: @@ -193,7 +198,7 @@ async def test_call_with_only_url_applies_defaults(web: FakeWeb) -> None: async def test_schema_violations_are_rejected_by_the_sdk( web: FakeWeb, arguments: dict[str, Any], offending: str ) -> None: - # The SDK validates against inputSchema before the handler runs. The rest + # call_tool validates against inputSchema before the tool runs. The rest # of the message is jsonschema's wording, which varies by version. async with connect() as (session, _): result = await call(session, arguments) @@ -206,7 +211,7 @@ async def test_schema_violations_are_rejected_by_the_sdk( @pytest.mark.parametrize("max_length", [1, 999999]) async def test_max_length_bounds_are_inclusive(web: FakeWeb, max_length: int) -> None: - # #1624: the edges of the inclusive range pass both the SDK's schema check + # #1624: the edges of the inclusive range pass both the schema check # and Fetch's own validation, and the page is fetched. web.add(ROBOTS, plain("", status=404)) web.add(PAGE, plain("hello")) @@ -230,8 +235,8 @@ async def test_invalid_url_is_rejected_by_pydantic(web: FakeWeb, url: str) -> No async def test_unknown_tool_name_is_rejected_without_fetching(web: FakeWeb) -> None: - # The SDK skips schema validation for a tool it has not listed, so the - # name check in call_tool is what stops an unknown name from fetching. + # call_tool skips schema validation for a tool it has not listed, so the + # name check in run_tool is what stops an unknown name from fetching. web.add(ROBOTS, plain("", status=404)) web.add(PAGE, plain("fetched anyway")) async with connect() as (session, _): @@ -949,7 +954,7 @@ async def test_get_prompt_without_url_is_a_jsonrpc_error( web: FakeWeb, arguments: dict[str, str] | None ) -> None: async with connect() as (session, _): - with pytest.raises(McpError) as excinfo: + with pytest.raises(MCPError) as excinfo: await session.get_prompt("fetch", arguments) assert wire(excinfo.value.error) == { "code": INVALID_PARAMS, @@ -966,7 +971,7 @@ async def test_get_prompt_with_unknown_name_is_a_jsonrpc_error( # whether or not a URL was given, and nothing is fetched. web.add(PAGE, plain("ok")) async with connect() as (session, _): - with pytest.raises(McpError) as excinfo: + with pytest.raises(MCPError) as excinfo: await session.get_prompt("nope", arguments) assert wire(excinfo.value.error) == { "code": INVALID_PARAMS, diff --git a/src/fetch/uv.lock b/src/fetch/uv.lock index b16b179358..dc2c712ded 100644 --- a/src/fetch/uv.lock +++ b/src/fetch/uv.lock @@ -1,11 +1,12 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version < '3.14' and sys_platform != 'win32'", + "(python_full_version < '3.12' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32')", ] [[package]] @@ -19,17 +20,16 @@ wheels = [ [[package]] name = "anyio" -version = "4.6.2.post1" +version = "4.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, - { name = "sniffio" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422, upload-time = "2024-10-14T14:31:44.021Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377, upload-time = "2024-10-14T14:31:42.623Z" }, + { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" }, ] [[package]] @@ -469,6 +469,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.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, + { name = "truststore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/f3/1db7aa2bc2524062192bb0e0323969492d1883152a232fe36eea65f4e35c/httpcore2-2.13.1.tar.gz", hash = "sha256:e0aa977abe17e69a3b820a24542a6fa88702676d83880b8d194dcd18408e5103", size = 68071, upload-time = "2026-09-23T07:47:22.372Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/ba/a4568248771ce81957bfb7cc600264a40fbcda092391ee1c415c50be4bea/httpcore2-2.13.1-py3-none-any.whl", hash = "sha256:e1e05d4f25f7d7d496bfb96748f6f4b67657b03da069b3a68c36069f3db73d0a", size = 83423, upload-time = "2026-09-23T07:47:19.365Z" }, +] + [[package]] name = "httpx" version = "0.27.2" @@ -491,12 +504,29 @@ socks = [ ] [[package]] -name = "httpx-sse" -version = "0.4.0" +name = "httpx2" +version = "2.13.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } +dependencies = [ + { name = "anyio", marker = "sys_platform != 'emscripten'" }, + { name = "httpcore2", marker = "sys_platform != 'emscripten'" }, + { name = "httpx2-jsfetch", marker = "python_full_version >= '3.12' and sys_platform == 'emscripten'" }, + { name = "idna" }, + { name = "truststore", marker = "sys_platform != 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/44/474bef2a0e9d90f1715d32cb98b0738695ca17ba324095fb2497ed7fbd59/httpx2-2.13.1.tar.gz", hash = "sha256:e48744a19e3af5ee48313d0ce5fe941d5422fae5705ea922a4aabf94d7800dfa", size = 100405, upload-time = "2026-09-23T07:47:23.052Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, + { url = "https://files.pythonhosted.org/packages/d8/9c/6fe8931fd9f381042a9e4c7d5a7b4cbf7016b252bec0c99a49fce42c3326/httpx2-2.13.1-py3-none-any.whl", hash = "sha256:6dff50fabc270ee5fd25d845d0b078ed20564579744d6d962850975996d2f9a4", size = 95597, upload-time = "2026-09-23T07:47:20.995Z" }, +] + +[[package]] +name = "httpx2-jsfetch" +version = "1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" }, ] [[package]] @@ -677,15 +707,15 @@ wheels = [ [[package]] name = "mcp" -version = "1.29.0" +version = "2.3.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'" }, @@ -695,9 +725,9 @@ dependencies = [ { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/d3/f9acc21dfc886e4f78e2add1a47db46ce16884346afde53f8a064c02c891/mcp-1.29.0.tar.gz", hash = "sha256:52d01f334de1868cc3bb2d6604931126a67631f99a6c5d3b82ba47290315ec36", size = 643148, upload-time = "2026-07-28T13:41:41.939Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/8d/e0d339616f4810e9051d4aba6887afab289ab1f81875fe908b606cdfd0e3/mcp-2.3.0.tar.gz", hash = "sha256:8b147a50441cf059dc88c684e0aeed3687f0aa0f39c6cde7b90330effd2b34d8", size = 4171955, upload-time = "2026-10-02T22:06:56.091Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/c8/248b201f6d753d69fd5d6506011abbb35a946d9142b2ae311a948fd0be3d/mcp-1.29.0-py3-none-any.whl", hash = "sha256:f5a075bb611f23d6f4d080c6a1699fa62772eebc562ba9e66b306ddde1c755f7", size = 223436, upload-time = "2026-07-28T13:41:40.337Z" }, + { url = "https://files.pythonhosted.org/packages/20/d7/4a2878f128df729db3ca04e71bfce26405cc2e0c9ebe19c592feeb4c61b9/mcp-2.3.0-py3-none-any.whl", hash = "sha256:dd0c44c089d16453e8ae31a3877a0054d7a2314caaa81f5e0541b9b1734b2377", size = 368664, upload-time = "2026-10-02T22:06:52.119Z" }, ] [[package]] @@ -706,6 +736,7 @@ version = "0.6.3" source = { editable = "." } dependencies = [ { name = "httpx", extra = ["socks"] }, + { name = "jsonschema" }, { name = "markdownify" }, { name = "mcp" }, { name = "protego" }, @@ -726,8 +757,9 @@ dev = [ [package.metadata] requires-dist = [ { name = "httpx", extras = ["socks"], specifier = ">=0.27" }, + { name = "jsonschema", specifier = ">=4.20.0" }, { name = "markdownify", specifier = ">=0.13.1" }, - { name = "mcp", specifier = ">=1.29.0,<2" }, + { name = "mcp", specifier = ">=2.2,<3" }, { name = "protego", specifier = ">=0.3.1" }, { name = "pydantic", specifier = ">=2.0.0" }, { name = "readabilipy", specifier = ">=0.2.0" }, @@ -743,6 +775,19 @@ dev = [ { name = "ruff", specifier = ">=0.7.3" }, ] +[[package]] +name = "mcp-types" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/2d/7c251e34207f6c51000312fc8839111ac45cfe02023f90b44e7f1051dd8e/mcp_types-2.3.0.tar.gz", hash = "sha256:d1e46549edb35ee19a94940fcee6d1addd7e589ab7ea92dda83f5d84781fc362", size = 65898, upload-time = "2026-10-02T22:06:57.863Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/48/32583066b155bb5d4507e7887dc88ee23118cc4de8e2a12870c49719e2a8/mcp_types-2.3.0-py3-none-any.whl", hash = "sha256:968efdbdaedfab06adae40d378a34395f1090c5921d4be3c9cde283aaf76d91d", size = 69108, upload-time = "2026-10-02T22:06:54.099Z" }, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -752,6 +797,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] +[[package]] +name = "opentelemetry-api" +version = "1.45.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + [[package]] name = "packaging" version = "26.0" @@ -921,19 +978,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, ] -[[package]] -name = "pydantic-settings" -version = "2.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b5/d4/9dfbe238f45ad8b168f5c96ee49a3df0598ce18a0795a983b419949ce65b/pydantic_settings-2.6.1.tar.gz", hash = "sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0", size = 75646, upload-time = "2024-11-01T11:00:05.17Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/f9/ff95fd7d760af42f647ea87f9b8a383d891cdb5e5dbd4613edaeb094252a/pydantic_settings-2.6.1-py3-none-any.whl", hash = "sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87", size = 28595, upload-time = "2024-11-01T11:00:02.64Z" }, -] - [[package]] name = "pygments" version = "2.20.0" @@ -1019,15 +1063,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] -[[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.32" @@ -1357,16 +1392,15 @@ wheels = [ [[package]] name = "sse-starlette" -version = "2.1.3" +version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "starlette" }, - { name = "uvicorn" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678, upload-time = "2024-08-01T08:52:50.248Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/be/0123026f719d1a7936f214a88b553bb5701e04ff2511147c1dab0c5035eb/sse_starlette-3.5.0.tar.gz", hash = "sha256:75de713aa8a9441513cc283220826da079d982770965b951e9437720e8bafdb2", size = 36794, upload-time = "2026-09-28T17:48:14.7Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/aa/36b271bc4fa1d2796311ee7c7283a3a1c348bad426d37293609ca4300eef/sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772", size = 9383, upload-time = "2024-08-01T08:52:48.659Z" }, + { url = "https://files.pythonhosted.org/packages/be/e4/cdda14023c316d71493bc54fdffc3dd006631b88866145c9d3cc33e0f1df/sse_starlette-3.5.0-py3-none-any.whl", hash = "sha256:3e6e1070df3f0f5d9cea81496de92dbb72f6721871d99748ece67441dd8b7997", size = 17407, upload-time = "2026-09-28T17:48:13.228Z" }, ] [[package]] @@ -1436,13 +1470,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, ] +[[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" +version = "4.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] [[package]] diff --git a/src/git/README.md b/src/git/README.md index 377cdac3eb..45e96b8121 100644 --- a/src/git/README.md +++ b/src/git/README.md @@ -8,7 +8,7 @@ A Model Context Protocol server for Git repository interaction and automation. T Source: https://github.com/modelcontextprotocol/servers/tree/main/src/git -Requires MCP Python SDK 1.x (`mcp>=1.29.0,<2`). SDK 2.0 renamed APIs this server uses. The port to v2 is in progress. +Requires MCP Python SDK 2.x (`mcp>=2.2,<3`). Please note that mcp-server-git is currently in early development. The functionality and available tools are subject to change and expansion as we continue to develop and improve the server. diff --git a/src/git/pyproject.toml b/src/git/pyproject.toml index 39f9f17b23..cf6374776e 100644 --- a/src/git/pyproject.toml +++ b/src/git/pyproject.toml @@ -18,7 +18,8 @@ classifiers = [ dependencies = [ "click>=8.1.7", "gitpython>=3.1.50", - "mcp>=1.29.0,<2", + "jsonschema>=4.20.0", + "mcp>=2.2,<3", "pydantic>=2.0.0", ] diff --git a/src/git/src/mcp_server_git/server.py b/src/git/src/mcp_server_git/server.py index 626e79b48c..a43ad717e7 100644 --- a/src/git/src/mcp_server_git/server.py +++ b/src/git/src/mcp_server_git/server.py @@ -2,9 +2,15 @@ from importlib.metadata import version from pathlib import Path from typing import Any, Optional -from mcp.server import Server +import jsonschema +from mcp.server import Server, ServerRequestContext from mcp.server.stdio import stdio_server from mcp.types import ( + CallToolRequestParams, + CallToolResult, + ContentBlock, + ListToolsResult, + PaginatedRequestParams, TextContent, Tool, ToolAnnotations, @@ -465,6 +471,13 @@ def git_branch( return branch_info +def tool_error(message: str) -> CallToolResult: + """A tool call that failed, as an `isError` result the model can read.""" + return CallToolResult( + content=[TextContent(type="text", text=message)], is_error=True + ) + + async def serve(repository: Path | None) -> None: logger = logging.getLogger(__name__) @@ -486,147 +499,148 @@ async def serve(repository: Path | None) -> None: repository = root logger.info(f"Using repository at {repository}") - server = Server("mcp-git", version=SERVER_VERSION) - - @server.list_tools() - async def list_tools() -> list[Tool]: - return [ - Tool( - name=GitTools.STATUS, - description="Shows the working tree status", - inputSchema=GitStatus.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), + tools = [ + Tool( + name=GitTools.STATUS, + description="Shows the working tree status", + input_schema=GitStatus.model_json_schema(), + annotations=ToolAnnotations( + read_only_hint=True, + destructive_hint=False, + idempotent_hint=True, + open_world_hint=False, ), - Tool( - name=GitTools.DIFF_UNSTAGED, - description="Shows changes in the working directory that are not yet staged", - inputSchema=GitDiffUnstaged.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), + ), + Tool( + name=GitTools.DIFF_UNSTAGED, + description="Shows changes in the working directory that are not yet staged", + input_schema=GitDiffUnstaged.model_json_schema(), + annotations=ToolAnnotations( + read_only_hint=True, + destructive_hint=False, + idempotent_hint=True, + open_world_hint=False, ), - Tool( - name=GitTools.DIFF_STAGED, - description="Shows changes that are staged for commit", - inputSchema=GitDiffStaged.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), + ), + Tool( + name=GitTools.DIFF_STAGED, + description="Shows changes that are staged for commit", + input_schema=GitDiffStaged.model_json_schema(), + annotations=ToolAnnotations( + read_only_hint=True, + destructive_hint=False, + idempotent_hint=True, + open_world_hint=False, ), - Tool( - name=GitTools.DIFF, - description="Shows differences between branches or commits", - inputSchema=GitDiff.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), + ), + Tool( + name=GitTools.DIFF, + description="Shows differences between branches or commits", + input_schema=GitDiff.model_json_schema(), + annotations=ToolAnnotations( + read_only_hint=True, + destructive_hint=False, + idempotent_hint=True, + open_world_hint=False, ), - Tool( - name=GitTools.COMMIT, - description="Records changes to the repository", - inputSchema=GitCommit.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=False, - destructiveHint=False, - idempotentHint=False, - openWorldHint=False, - ), + ), + Tool( + name=GitTools.COMMIT, + description="Records changes to the repository", + input_schema=GitCommit.model_json_schema(), + annotations=ToolAnnotations( + read_only_hint=False, + destructive_hint=False, + idempotent_hint=False, + open_world_hint=False, ), - Tool( - name=GitTools.ADD, - description="Adds file contents to the staging area", - inputSchema=GitAdd.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=False, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), + ), + Tool( + name=GitTools.ADD, + description="Adds file contents to the staging area", + input_schema=GitAdd.model_json_schema(), + annotations=ToolAnnotations( + read_only_hint=False, + destructive_hint=False, + idempotent_hint=True, + open_world_hint=False, ), - Tool( - name=GitTools.RESET, - description="Unstages all staged changes", - inputSchema=GitReset.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=False, - destructiveHint=True, - idempotentHint=True, - openWorldHint=False, - ), + ), + Tool( + name=GitTools.RESET, + description="Unstages all staged changes", + input_schema=GitReset.model_json_schema(), + annotations=ToolAnnotations( + read_only_hint=False, + destructive_hint=True, + idempotent_hint=True, + open_world_hint=False, ), - Tool( - name=GitTools.LOG, - description="Shows the commit logs", - inputSchema=GitLog.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), + ), + Tool( + name=GitTools.LOG, + description="Shows the commit logs", + input_schema=GitLog.model_json_schema(), + annotations=ToolAnnotations( + read_only_hint=True, + destructive_hint=False, + idempotent_hint=True, + open_world_hint=False, ), - Tool( - name=GitTools.CREATE_BRANCH, - description="Creates a new branch from an optional base branch", - inputSchema=GitCreateBranch.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=False, - destructiveHint=False, - idempotentHint=False, - openWorldHint=False, - ), + ), + Tool( + name=GitTools.CREATE_BRANCH, + description="Creates a new branch from an optional base branch", + input_schema=GitCreateBranch.model_json_schema(), + annotations=ToolAnnotations( + read_only_hint=False, + destructive_hint=False, + idempotent_hint=False, + open_world_hint=False, ), - Tool( - name=GitTools.CHECKOUT, - description="Switches branches", - inputSchema=GitCheckout.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=False, - destructiveHint=False, - idempotentHint=False, - openWorldHint=False, - ), + ), + Tool( + name=GitTools.CHECKOUT, + description="Switches branches", + input_schema=GitCheckout.model_json_schema(), + annotations=ToolAnnotations( + read_only_hint=False, + destructive_hint=False, + idempotent_hint=False, + open_world_hint=False, ), - Tool( - name=GitTools.SHOW, - description="Shows the contents of a commit, or of a file or directory given as :", - inputSchema=GitShow.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), + ), + Tool( + name=GitTools.SHOW, + description="Shows the contents of a commit, or of a file or directory given as :", + input_schema=GitShow.model_json_schema(), + annotations=ToolAnnotations( + read_only_hint=True, + destructive_hint=False, + idempotent_hint=True, + open_world_hint=False, ), - Tool( - name=GitTools.BRANCH, - description="List Git branches", - inputSchema=GitBranch.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), + ), + Tool( + name=GitTools.BRANCH, + description="List Git branches", + input_schema=GitBranch.model_json_schema(), + annotations=ToolAnnotations( + read_only_hint=True, + destructive_hint=False, + idempotent_hint=True, + open_world_hint=False, ), - ] + ), + ] + schemas = {tool.name: tool.input_schema for tool in tools} - @server.call_tool() - async def call_tool(name: str, arguments: dict) -> list[TextContent]: + async def list_tools( + ctx: ServerRequestContext, params: PaginatedRequestParams | None + ) -> ListToolsResult: + return ListToolsResult(tools=tools) + + async def run_tool(name: str, arguments: dict[str, Any]) -> list[ContentBlock]: # Reject an unknown tool before reading its arguments, so a call # without repo_path still reports "Unknown tool" (#4994). if name not in {tool.value for tool in GitTools}: @@ -720,6 +734,30 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]: case _: # pragma: no cover raise ValueError(f"Unknown tool: {name}") + async def call_tool( + ctx: ServerRequestContext, params: CallToolRequestParams + ) -> CallToolResult: + # SDK v2's low-level server neither validates arguments nor turns a + # handler exception into an isError result, both of which SDK v1 did. + # Both are done here, with v1's messages, so the wire is unchanged. + arguments = params.arguments or {} + schema = schemas.get(params.name) + if schema is not None: + try: + jsonschema.validate(instance=arguments, schema=schema) + except jsonschema.ValidationError as e: + return tool_error(f"Input validation error: {e.message}") + try: + return CallToolResult(content=await run_tool(params.name, arguments)) + except Exception as e: + return tool_error(str(e)) + + server = Server( + "mcp-git", + version=SERVER_VERSION, + on_list_tools=list_tools, + on_call_tool=call_tool, + ) options = server.create_initialization_options() async with stdio_server() as (read_stream, write_stream): await server.run(read_stream, write_stream, options) diff --git a/src/git/tests/conftest.py b/src/git/tests/conftest.py index 8b8f984bd1..b68086bdc0 100644 --- a/src/git/tests/conftest.py +++ b/src/git/tests/conftest.py @@ -130,7 +130,12 @@ async def fake_stdio_server() -> AsyncIterator[Any]: def wire(model: BaseModel) -> dict[str, Any]: """A result as it travels on the wire, independent of SDK attribute names.""" - return model.model_dump(by_alias=True, mode="json", exclude_none=True) + # exclude_unset: SDK v2 result models default fields the 2025-11-25 wire + # does not carry (resultType, ttlMs, cacheScope), so only what the server + # actually sent is compared. + return model.model_dump( + by_alias=True, mode="json", exclude_none=True, exclude_unset=True + ) def text_result(text: str, *, is_error: bool = False) -> dict[str, Any]: diff --git a/src/git/tests/test_entrypoints.py b/src/git/tests/test_entrypoints.py index 9c765beeb9..8b99cc4078 100644 --- a/src/git/tests/test_entrypoints.py +++ b/src/git/tests/test_entrypoints.py @@ -51,7 +51,7 @@ async def test_serve_resolves_repository_subdirectory_to_root( with caplog.at_level(logging.INFO, logger="mcp_server_git.server"): async with connect(sub) as session: result = await session.call_tool("git_status", {"repo_path": str(root)}) - assert not result.isError + assert not result.is_error assert f"Resolved --repository {sub} to repository root {root}" in caplog.text assert f"Using repository at {root}" in caplog.text diff --git a/src/git/tests/test_protocol.py b/src/git/tests/test_protocol.py index efc95fb895..39c0bc749f 100644 --- a/src/git/tests/test_protocol.py +++ b/src/git/tests/test_protocol.py @@ -17,7 +17,7 @@ import git import pytest -from mcp.shared.context import RequestContext +from mcp.client.session import ClientRequestContext from mcp.types import ListRootsResult, Root from pydantic import FileUrl @@ -63,10 +63,9 @@ async def test_initialize_reports_package_version_as_server_version(repo: git.Re "version": version("mcp-server-git"), } assert version("mcp") != version("mcp-server-git") - assert init["capabilities"] == { - "experimental": {}, - "tools": {"listChanged": False}, - } + # Behavior change in the SDK v2 port (#4851): v2 omits the empty + # `experimental: {}` object v1 always sent. It advertised nothing. + assert init["capabilities"] == {"tools": {"listChanged": False}} async def test_list_tools_matches_wire_snapshot(): @@ -381,7 +380,7 @@ async def test_git_add_file_starting_with_dash_is_a_path(repo: git.Repo): async def test_git_add_empty_list_is_rejected(repo: git.Repo): # #4763: `files: []` would run `git add --`, a no-op. The schema's - # minItems makes the SDK reject it before the tool runs. + # minItems makes call_tool reject it before the tool runs. root = root_of(repo) (root / "test.txt").write_text("edited\n", newline="\n") result = await call(None, "git_add", {"repo_path": str(root), "files": []}) @@ -1114,6 +1113,9 @@ def resolve(self: Path, strict: bool = False) -> Path: # -------------------------------------------------------------------------- +# The client side of this test offers Roots, which SDK v2 deprecates as of +# 2026-07-28 (SEP-2577); the warning is about the test's client, not the server. +@pytest.mark.filterwarnings("ignore::mcp.shared.exceptions.MCPDeprecationWarning") async def test_server_never_requests_roots(tmp_path: Path, repo: git.Repo): # The server ignores Roots entirely: it never sends roots/list, not even # after notifications/roots/list_changed, and a repository outside every @@ -1123,7 +1125,7 @@ async def test_server_never_requests_roots(tmp_path: Path, repo: git.Repo): elsewhere.mkdir() calls: list[object] = [] - async def list_roots(context: RequestContext[Any, Any]) -> ListRootsResult: + async def list_roots(context: ClientRequestContext) -> ListRootsResult: calls.append(context) return ListRootsResult(roots=[Root(uri=FileUrl(elsewhere.as_uri()))]) diff --git a/src/git/uv.lock b/src/git/uv.lock index caa3d61b5e..92d67b2a77 100644 --- a/src/git/uv.lock +++ b/src/git/uv.lock @@ -1,11 +1,12 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version < '3.14' and sys_platform != 'win32'", + "(python_full_version < '3.12' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32')", ] [[package]] @@ -19,17 +20,16 @@ wheels = [ [[package]] name = "anyio" -version = "4.6.2.post1" +version = "4.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, - { name = "sniffio" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422, upload-time = "2024-10-14T14:31:44.021Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377, upload-time = "2024-10-14T14:31:42.623Z" }, + { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" }, ] [[package]] @@ -50,15 +50,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, ] -[[package]] -name = "certifi" -version = "2024.8.30" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507, upload-time = "2024-08-30T01:55:04.365Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321, upload-time = "2024-08-30T01:55:02.591Z" }, -] - [[package]] name = "cffi" version = "2.0.0" @@ -395,41 +386,42 @@ wheels = [ ] [[package]] -name = "httpcore" -version = "1.0.9" +name = "httpcore2" +version = "2.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "certifi" }, { name = "h11" }, + { name = "truststore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/f3/1db7aa2bc2524062192bb0e0323969492d1883152a232fe36eea65f4e35c/httpcore2-2.13.1.tar.gz", hash = "sha256:e0aa977abe17e69a3b820a24542a6fa88702676d83880b8d194dcd18408e5103", size = 68071, upload-time = "2026-09-23T07:47:22.372Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/09/ba/a4568248771ce81957bfb7cc600264a40fbcda092391ee1c415c50be4bea/httpcore2-2.13.1-py3-none-any.whl", hash = "sha256:e1e05d4f25f7d7d496bfb96748f6f4b67657b03da069b3a68c36069f3db73d0a", size = 83423, upload-time = "2026-09-23T07:47:19.365Z" }, ] [[package]] -name = "httpx" -version = "0.27.2" +name = "httpx2" +version = "2.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, + { name = "anyio", marker = "sys_platform != 'emscripten'" }, + { name = "httpcore2", marker = "sys_platform != 'emscripten'" }, + { name = "httpx2-jsfetch", marker = "python_full_version >= '3.12' and sys_platform == 'emscripten'" }, { name = "idna" }, - { name = "sniffio" }, + { name = "truststore", marker = "sys_platform != 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189, upload-time = "2024-08-27T12:54:01.334Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/44/474bef2a0e9d90f1715d32cb98b0738695ca17ba324095fb2497ed7fbd59/httpx2-2.13.1.tar.gz", hash = "sha256:e48744a19e3af5ee48313d0ce5fe941d5422fae5705ea922a4aabf94d7800dfa", size = 100405, upload-time = "2026-09-23T07:47:23.052Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395, upload-time = "2024-08-27T12:53:59.653Z" }, + { url = "https://files.pythonhosted.org/packages/d8/9c/6fe8931fd9f381042a9e4c7d5a7b4cbf7016b252bec0c99a49fce42c3326/httpx2-2.13.1-py3-none-any.whl", hash = "sha256:6dff50fabc270ee5fd25d845d0b078ed20564579744d6d962850975996d2f9a4", size = 95597, upload-time = "2026-09-23T07:47:20.995Z" }, ] [[package]] -name = "httpx-sse" -version = "0.4.0" +name = "httpx2-jsfetch" +version = "1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, + { url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" }, ] [[package]] @@ -479,15 +471,15 @@ wheels = [ [[package]] name = "mcp" -version = "1.29.0" +version = "2.3.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'" }, @@ -497,9 +489,9 @@ dependencies = [ { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/d3/f9acc21dfc886e4f78e2add1a47db46ce16884346afde53f8a064c02c891/mcp-1.29.0.tar.gz", hash = "sha256:52d01f334de1868cc3bb2d6604931126a67631f99a6c5d3b82ba47290315ec36", size = 643148, upload-time = "2026-07-28T13:41:41.939Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/8d/e0d339616f4810e9051d4aba6887afab289ab1f81875fe908b606cdfd0e3/mcp-2.3.0.tar.gz", hash = "sha256:8b147a50441cf059dc88c684e0aeed3687f0aa0f39c6cde7b90330effd2b34d8", size = 4171955, upload-time = "2026-10-02T22:06:56.091Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/c8/248b201f6d753d69fd5d6506011abbb35a946d9142b2ae311a948fd0be3d/mcp-1.29.0-py3-none-any.whl", hash = "sha256:f5a075bb611f23d6f4d080c6a1699fa62772eebc562ba9e66b306ddde1c755f7", size = 223436, upload-time = "2026-07-28T13:41:40.337Z" }, + { url = "https://files.pythonhosted.org/packages/20/d7/4a2878f128df729db3ca04e71bfce26405cc2e0c9ebe19c592feeb4c61b9/mcp-2.3.0-py3-none-any.whl", hash = "sha256:dd0c44c089d16453e8ae31a3877a0054d7a2314caaa81f5e0541b9b1734b2377", size = 368664, upload-time = "2026-10-02T22:06:52.119Z" }, ] [[package]] @@ -509,6 +501,7 @@ source = { editable = "." } dependencies = [ { name = "click" }, { name = "gitpython" }, + { name = "jsonschema" }, { name = "mcp" }, { name = "pydantic" }, ] @@ -526,7 +519,8 @@ dev = [ requires-dist = [ { name = "click", specifier = ">=8.1.7" }, { name = "gitpython", specifier = ">=3.1.50" }, - { name = "mcp", specifier = ">=1.29.0,<2" }, + { name = "jsonschema", specifier = ">=4.20.0" }, + { name = "mcp", specifier = ">=2.2,<3" }, { name = "pydantic", specifier = ">=2.0.0" }, ] @@ -539,6 +533,19 @@ dev = [ { name = "ruff", specifier = ">=0.7.3" }, ] +[[package]] +name = "mcp-types" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/2d/7c251e34207f6c51000312fc8839111ac45cfe02023f90b44e7f1051dd8e/mcp_types-2.3.0.tar.gz", hash = "sha256:d1e46549edb35ee19a94940fcee6d1addd7e589ab7ea92dda83f5d84781fc362", size = 65898, upload-time = "2026-10-02T22:06:57.863Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/48/32583066b155bb5d4507e7887dc88ee23118cc4de8e2a12870c49719e2a8/mcp_types-2.3.0-py3-none-any.whl", hash = "sha256:968efdbdaedfab06adae40d378a34395f1090c5921d4be3c9cde283aaf76d91d", size = 69108, upload-time = "2026-10-02T22:06:54.099Z" }, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -548,6 +555,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] +[[package]] +name = "opentelemetry-api" +version = "1.45.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + [[package]] name = "packaging" version = "24.2" @@ -708,20 +727,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, ] -[[package]] -name = "pydantic-settings" -version = "2.14.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5c/b5/8f48e906c3e0205276e8bd8cb7512217a87b2685304d64be27cad5b3019f/pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f", size = 237700, upload-time = "2026-06-19T13:44:56.324Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/c1/6e422f34e569cf8e18df68d1939c81c099d2b61e4f7d9621c8a77560799c/pydantic_settings-2.14.2-py3-none-any.whl", hash = "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440", size = 61715, upload-time = "2026-06-19T13:44:55.02Z" }, -] - [[package]] name = "pygments" version = "2.20.0" @@ -807,15 +812,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] -[[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.32" @@ -1017,27 +1013,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/a5/10f97f73544edcdef54409f1d839f6049a0d79df68adbc1ceb24d1aaca42/smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da", size = 24282, upload-time = "2023-09-17T11:35:03.253Z" }, ] -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, -] - [[package]] name = "sse-starlette" -version = "2.1.3" +version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "starlette" }, - { name = "uvicorn" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678, upload-time = "2024-08-01T08:52:50.248Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/be/0123026f719d1a7936f214a88b553bb5701e04ff2511147c1dab0c5035eb/sse_starlette-3.5.0.tar.gz", hash = "sha256:75de713aa8a9441513cc283220826da079d982770965b951e9437720e8bafdb2", size = 36794, upload-time = "2026-09-28T17:48:14.7Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/aa/36b271bc4fa1d2796311ee7c7283a3a1c348bad426d37293609ca4300eef/sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772", size = 9383, upload-time = "2024-08-01T08:52:48.659Z" }, + { url = "https://files.pythonhosted.org/packages/be/e4/cdda14023c316d71493bc54fdffc3dd006631b88866145c9d3cc33e0f1df/sse_starlette-3.5.0-py3-none-any.whl", hash = "sha256:3e6e1070df3f0f5d9cea81496de92dbb72f6721871d99748ece67441dd8b7997", size = 17407, upload-time = "2026-09-28T17:48:13.228Z" }, ] [[package]] @@ -1092,13 +1078,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] +[[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" +version = "4.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] [[package]] diff --git a/src/time/README.md b/src/time/README.md index 43775f71fa..2b878eab81 100644 --- a/src/time/README.md +++ b/src/time/README.md @@ -6,7 +6,7 @@ A Model Context Protocol server that provides time and timezone conversion capab Source: https://github.com/modelcontextprotocol/servers/tree/main/src/time -Requires MCP Python SDK 1.x (`mcp>=1.29.0,<2`). SDK 2.0 renamed APIs this server uses. The port to v2 is in progress. +Requires MCP Python SDK 2.x (`mcp>=2.2,<3`). ### Available Tools diff --git a/src/time/pyproject.toml b/src/time/pyproject.toml index 44d24a99a0..014dbf629e 100644 --- a/src/time/pyproject.toml +++ b/src/time/pyproject.toml @@ -17,7 +17,8 @@ classifiers = [ "Programming Language :: Python :: 3.10", ] dependencies = [ - "mcp>=1.29.0,<2", + "jsonschema>=4.20.0", + "mcp>=2.2,<3", "pydantic>=2.0.0", "tzdata>=2024.2", "tzlocal>=5.3.1", diff --git a/src/time/src/mcp_server_time/server.py b/src/time/src/mcp_server_time/server.py index 461bc4a2ae..57fb2763df 100644 --- a/src/time/src/mcp_server_time/server.py +++ b/src/time/src/mcp_server_time/server.py @@ -3,23 +3,26 @@ from enum import Enum import json import sys -from typing import Sequence +from typing import Any from zoneinfo import ZoneInfo, ZoneInfoNotFoundError from tzlocal import get_localzone_name # ← returns "Europe/Paris", etc. -from mcp.server import Server +import jsonschema +from mcp.server import Server, ServerRequestContext from mcp.server.stdio import stdio_server from mcp.types import ( + CallToolRequestParams, + CallToolResult, + ContentBlock, + ListToolsResult, + PaginatedRequestParams, Tool, ToolAnnotations, TextContent, - ImageContent, - EmbeddedResource, - ErrorData, INVALID_PARAMS, ) -from mcp.shared.exceptions import McpError +from mcp.shared.exceptions import MCPError from pydantic import BaseModel @@ -69,9 +72,7 @@ def get_zoneinfo(timezone_name: str) -> ZoneInfo: try: return ZoneInfo(timezone_name) except Exception as e: - raise McpError( - ErrorData(code=INVALID_PARAMS, message=f"Invalid timezone: {str(e)}") - ) + raise MCPError(code=INVALID_PARAMS, message=f"Invalid timezone: {str(e)}") class TimeServer: @@ -146,8 +147,14 @@ def convert_time( ) +def tool_error(message: str) -> CallToolResult: + """A tool call that failed, as an `isError` result the model can read.""" + return CallToolResult( + content=[TextContent(type="text", text=message)], is_error=True + ) + + async def serve(local_timezone: str | None = None) -> None: - server = Server("mcp-time", version=SERVER_VERSION) time_server = TimeServer() if local_timezone: # Fail before the transport opens, with one line naming the bad value @@ -163,64 +170,65 @@ async def serve(local_timezone: str | None = None) -> None: ) local_tz = str(get_local_tz(local_timezone)) - @server.list_tools() - async def list_tools() -> list[Tool]: - """List available time tools.""" - return [ - Tool( - name=TimeTools.GET_CURRENT_TIME.value, - description="Get current time in a specific timezone", - inputSchema={ - "type": "object", - "properties": { - "timezone": { - "type": "string", - "description": f"IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use '{local_tz}' as local timezone if no timezone provided by the user.", - } - }, - "required": ["timezone"], + tools = [ + Tool( + name=TimeTools.GET_CURRENT_TIME.value, + description="Get current time in a specific timezone", + input_schema={ + "type": "object", + "properties": { + "timezone": { + "type": "string", + "description": f"IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use '{local_tz}' as local timezone if no timezone provided by the user.", + } }, - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), + "required": ["timezone"], + }, + annotations=ToolAnnotations( + read_only_hint=True, + destructive_hint=False, + idempotent_hint=True, + open_world_hint=False, ), - Tool( - name=TimeTools.CONVERT_TIME.value, - description="Convert time between timezones", - inputSchema={ - "type": "object", - "properties": { - "source_timezone": { - "type": "string", - "description": f"Source IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use '{local_tz}' as local timezone if no source timezone provided by the user.", - }, - "time": { - "type": "string", - "description": "Time to convert in 24-hour format (HH:MM)", - }, - "target_timezone": { - "type": "string", - "description": f"Target IANA timezone name (e.g., 'Asia/Tokyo', 'America/San_Francisco'). Use '{local_tz}' as local timezone if no target timezone provided by the user.", - }, + ), + Tool( + name=TimeTools.CONVERT_TIME.value, + description="Convert time between timezones", + input_schema={ + "type": "object", + "properties": { + "source_timezone": { + "type": "string", + "description": f"Source IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use '{local_tz}' as local timezone if no source timezone provided by the user.", + }, + "time": { + "type": "string", + "description": "Time to convert in 24-hour format (HH:MM)", + }, + "target_timezone": { + "type": "string", + "description": f"Target IANA timezone name (e.g., 'Asia/Tokyo', 'America/San_Francisco'). Use '{local_tz}' as local timezone if no target timezone provided by the user.", }, - "required": ["source_timezone", "time", "target_timezone"], }, - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), + "required": ["source_timezone", "time", "target_timezone"], + }, + annotations=ToolAnnotations( + read_only_hint=True, + destructive_hint=False, + idempotent_hint=True, + open_world_hint=False, ), - ] + ), + ] + schemas = {tool.name: tool.input_schema for tool in tools} - @server.call_tool() - async def call_tool( - name: str, arguments: dict - ) -> Sequence[TextContent | ImageContent | EmbeddedResource]: + async def list_tools( + ctx: ServerRequestContext, params: PaginatedRequestParams | None + ) -> ListToolsResult: + """List available time tools.""" + return ListToolsResult(tools=tools) + + def run_tool(name: str, arguments: dict[str, Any]) -> list[ContentBlock]: """Handle tool calls for time queries.""" try: match name: @@ -236,7 +244,7 @@ async def call_tool( k in arguments for k in ["source_timezone", "time", "target_timezone"] ): - raise ValueError( # pragma: no cover # unreachable: the SDK rejects a missing key against inputSchema first + raise ValueError( # pragma: no cover # unreachable: call_tool rejects a missing key against inputSchema first "Missing required arguments" ) for key in ["source_timezone", "target_timezone"]: @@ -258,6 +266,30 @@ async def call_tool( except Exception as e: raise ValueError(f"Error processing mcp-server-time query: {str(e)}") + async def call_tool( + ctx: ServerRequestContext, params: CallToolRequestParams + ) -> CallToolResult: + # SDK v2's low-level server neither validates arguments nor turns a + # handler exception into an isError result, both of which SDK v1 did. + # Both are done here, with v1's messages, so the wire is unchanged. + arguments = params.arguments or {} + schema = schemas.get(params.name) + if schema is not None: + try: + jsonschema.validate(instance=arguments, schema=schema) + except jsonschema.ValidationError as e: + return tool_error(f"Input validation error: {e.message}") + try: + return CallToolResult(content=run_tool(params.name, arguments)) + except Exception as e: + return tool_error(str(e)) + + server = Server( + "mcp-time", + version=SERVER_VERSION, + on_list_tools=list_tools, + on_call_tool=call_tool, + ) options = server.create_initialization_options() async with stdio_server() as (read_stream, write_stream): await server.run(read_stream, write_stream, options) diff --git a/src/time/tests/test_protocol.py b/src/time/tests/test_protocol.py index f76aa58051..e3f99f6d9e 100644 --- a/src/time/tests/test_protocol.py +++ b/src/time/tests/test_protocol.py @@ -3,7 +3,7 @@ # # Each test runs the real `serve()` coroutine with `stdio_server` swapped for # an in-memory stream pair, and talks to it with a real `ClientSession`. That -# exercises what a client actually sees: the SDK's input validation, its +# exercises what a client actually sees: the server's input validation, its # folding of handler exceptions into `isError` results, the advertised tool # schemas and the initialize handshake. The pure helpers in `server.py` are # covered directly in `test_server.py`. @@ -54,7 +54,12 @@ def now(cls, tz: tzinfo | None = None): def wire(model: BaseModel) -> Any: """Serialize an SDK model the way it travels over the wire.""" - return model.model_dump(by_alias=True, mode="json", exclude_none=True) + # exclude_unset: SDK v2 result models default fields the 2025-11-25 wire + # does not carry (resultType, ttlMs, cacheScope), so only what the server + # actually sent is compared. + return model.model_dump( + by_alias=True, mode="json", exclude_none=True, exclude_unset=True + ) @asynccontextmanager @@ -131,7 +136,9 @@ async def test_initialize_reports_server_info_and_capabilities() -> None: } assert init["serverInfo"]["version"] != version("mcp") # Only tools are advertised; no resources, prompts or logging. - assert init["capabilities"] == {"experimental": {}, "tools": {"listChanged": False}} + # Behavior change in the SDK v2 port (#4851): v2 omits the empty + # `experimental: {}` object v1 always sent. It advertised nothing. + assert init["capabilities"] == {"tools": {"listChanged": False}} assert "instructions" not in init @@ -467,7 +474,7 @@ async def test_get_current_time_does_not_default_to_the_local_timezone() -> None @pytest.mark.parametrize( "arguments,expected", [ - # Rejected by the SDK's input validation against inputSchema, before + # Rejected by call_tool's input validation against inputSchema, before # the handler runs. ({}, error_result("Input validation error: 'timezone' is a required property")), ( @@ -478,7 +485,7 @@ async def test_get_current_time_does_not_default_to_the_local_timezone() -> None {"timezone": None}, error_result("Input validation error: None is not of type 'string'"), ), - # Raised by the handler and folded into an error result by the SDK. + # Raised by the handler and folded into an error result by call_tool. ({"timezone": ""}, handler_error("Missing required argument: timezone")), ( {"timezone": "Invalid/Timezone"}, @@ -817,7 +824,7 @@ async def test_convert_time_ignores_extra_arguments() -> None: @pytest.mark.parametrize( "arguments,expected", [ - # Rejected by the SDK's input validation, before the handler runs. A + # Rejected by call_tool's input validation, before the handler runs. A # missing key never reaches the handler's own "Missing required # arguments" check, which is why that branch is marked unreachable. ( @@ -899,8 +906,8 @@ async def test_convert_time_checks_timezones_before_the_time_string() -> None: async def test_unknown_tool_is_an_error_result_not_a_protocol_error() -> None: - # The SDK skips validation for an unlisted tool (and logs a warning), then - # the handler's fallthrough raises, which the SDK folds into `isError`. + # call_tool skips validation for an unlisted tool, then the handler's + # fallthrough raises, which call_tool folds into `isError`. async with connected() as session: result = await call(session, "no_such_tool", {"timezone": "UTC"}) assert result == handler_error("Unknown tool: no_such_tool") diff --git a/src/time/tests/test_server.py b/src/time/tests/test_server.py index a6a6c675f4..a197daa07a 100644 --- a/src/time/tests/test_server.py +++ b/src/time/tests/test_server.py @@ -1,5 +1,5 @@ from freezegun import freeze_time -from mcp.shared.exceptions import McpError +from mcp.shared.exceptions import MCPError import pytest from unittest.mock import patch from zoneinfo import ZoneInfo @@ -84,7 +84,7 @@ def test_get_current_time(test_time, timezone, expected): def test_get_current_time_with_invalid_timezone(): time_server = TimeServer() with pytest.raises( - McpError, + MCPError, match=r"Invalid timezone: 'No time zone found with key Invalid/Timezone'", ): time_server.get_current_time("Invalid/Timezone") @@ -115,7 +115,7 @@ def test_get_current_time_with_invalid_timezone(): ) def test_convert_time_errors(source_tz, time_str, target_tz, expected_error): time_server = TimeServer() - with pytest.raises((McpError, ValueError), match=expected_error): + with pytest.raises((MCPError, ValueError), match=expected_error): time_server.convert_time(source_tz, time_str, target_tz) diff --git a/src/time/uv.lock b/src/time/uv.lock index a54c221cad..d5201d4788 100644 --- a/src/time/uv.lock +++ b/src/time/uv.lock @@ -1,11 +1,12 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version < '3.14' and sys_platform != 'win32'", + "(python_full_version < '3.12' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32')", ] [[package]] @@ -19,17 +20,16 @@ wheels = [ [[package]] name = "anyio" -version = "4.6.2.post1" +version = "4.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, - { name = "sniffio" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422, upload-time = "2024-10-14T14:31:44.021Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377, upload-time = "2024-10-14T14:31:42.623Z" }, + { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" }, ] [[package]] @@ -50,15 +50,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, ] -[[package]] -name = "certifi" -version = "2024.8.30" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507, upload-time = "2024-08-30T01:55:04.365Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321, upload-time = "2024-08-30T01:55:02.591Z" }, -] - [[package]] name = "cffi" version = "2.0.0" @@ -383,40 +374,42 @@ wheels = [ ] [[package]] -name = "httpcore" -version = "1.0.9" +name = "httpcore2" +version = "2.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "certifi" }, { name = "h11" }, + { name = "truststore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/f3/1db7aa2bc2524062192bb0e0323969492d1883152a232fe36eea65f4e35c/httpcore2-2.13.1.tar.gz", hash = "sha256:e0aa977abe17e69a3b820a24542a6fa88702676d83880b8d194dcd18408e5103", size = 68071, upload-time = "2026-09-23T07:47:22.372Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/09/ba/a4568248771ce81957bfb7cc600264a40fbcda092391ee1c415c50be4bea/httpcore2-2.13.1-py3-none-any.whl", hash = "sha256:e1e05d4f25f7d7d496bfb96748f6f4b67657b03da069b3a68c36069f3db73d0a", size = 83423, upload-time = "2026-09-23T07:47:19.365Z" }, ] [[package]] -name = "httpx" -version = "0.28.0" +name = "httpx2" +version = "2.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, + { name = "anyio", marker = "sys_platform != 'emscripten'" }, + { name = "httpcore2", marker = "sys_platform != 'emscripten'" }, + { name = "httpx2-jsfetch", marker = "python_full_version >= '3.12' and sys_platform == 'emscripten'" }, { name = "idna" }, + { name = "truststore", marker = "sys_platform != 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/df/676b7cf674dd1bdc71a64ad393c89879f75e4a0ab8395165b498262ae106/httpx-0.28.0.tar.gz", hash = "sha256:0858d3bab51ba7e386637f22a61d8ccddaeec5f3fe4209da3a6168dbb91573e0", size = 141307, upload-time = "2024-11-28T14:54:56.977Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/44/474bef2a0e9d90f1715d32cb98b0738695ca17ba324095fb2497ed7fbd59/httpx2-2.13.1.tar.gz", hash = "sha256:e48744a19e3af5ee48313d0ce5fe941d5422fae5705ea922a4aabf94d7800dfa", size = 100405, upload-time = "2026-09-23T07:47:23.052Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/fb/a19866137577ba60c6d8b69498dc36be479b13ba454f691348ddf428f185/httpx-0.28.0-py3-none-any.whl", hash = "sha256:dc0b419a0cfeb6e8b34e85167c0da2671206f5095f1baa9663d23bcfd6b535fc", size = 73551, upload-time = "2024-11-28T14:54:55.141Z" }, + { url = "https://files.pythonhosted.org/packages/d8/9c/6fe8931fd9f381042a9e4c7d5a7b4cbf7016b252bec0c99a49fce42c3326/httpx2-2.13.1-py3-none-any.whl", hash = "sha256:6dff50fabc270ee5fd25d845d0b078ed20564579744d6d962850975996d2f9a4", size = 95597, upload-time = "2026-09-23T07:47:20.995Z" }, ] [[package]] -name = "httpx-sse" -version = "0.4.0" +name = "httpx2-jsfetch" +version = "1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, + { url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" }, ] [[package]] @@ -466,15 +459,15 @@ wheels = [ [[package]] name = "mcp" -version = "1.29.0" +version = "2.3.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'" }, @@ -484,9 +477,9 @@ dependencies = [ { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/d3/f9acc21dfc886e4f78e2add1a47db46ce16884346afde53f8a064c02c891/mcp-1.29.0.tar.gz", hash = "sha256:52d01f334de1868cc3bb2d6604931126a67631f99a6c5d3b82ba47290315ec36", size = 643148, upload-time = "2026-07-28T13:41:41.939Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/8d/e0d339616f4810e9051d4aba6887afab289ab1f81875fe908b606cdfd0e3/mcp-2.3.0.tar.gz", hash = "sha256:8b147a50441cf059dc88c684e0aeed3687f0aa0f39c6cde7b90330effd2b34d8", size = 4171955, upload-time = "2026-10-02T22:06:56.091Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/c8/248b201f6d753d69fd5d6506011abbb35a946d9142b2ae311a948fd0be3d/mcp-1.29.0-py3-none-any.whl", hash = "sha256:f5a075bb611f23d6f4d080c6a1699fa62772eebc562ba9e66b306ddde1c755f7", size = 223436, upload-time = "2026-07-28T13:41:40.337Z" }, + { url = "https://files.pythonhosted.org/packages/20/d7/4a2878f128df729db3ca04e71bfce26405cc2e0c9ebe19c592feeb4c61b9/mcp-2.3.0-py3-none-any.whl", hash = "sha256:dd0c44c089d16453e8ae31a3877a0054d7a2314caaa81f5e0541b9b1734b2377", size = 368664, upload-time = "2026-10-02T22:06:52.119Z" }, ] [[package]] @@ -494,6 +487,7 @@ name = "mcp-server-time" version = "0.6.2" source = { editable = "." } dependencies = [ + { name = "jsonschema" }, { name = "mcp" }, { name = "pydantic" }, { name = "tzdata" }, @@ -512,7 +506,8 @@ dev = [ [package.metadata] requires-dist = [ - { name = "mcp", specifier = ">=1.29.0,<2" }, + { name = "jsonschema", specifier = ">=4.20.0" }, + { name = "mcp", specifier = ">=2.2,<3" }, { name = "pydantic", specifier = ">=2.0.0" }, { name = "tzdata", specifier = ">=2024.2" }, { name = "tzlocal", specifier = ">=5.3.1" }, @@ -528,6 +523,19 @@ dev = [ { name = "ruff", specifier = ">=0.8.1" }, ] +[[package]] +name = "mcp-types" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/2d/7c251e34207f6c51000312fc8839111ac45cfe02023f90b44e7f1051dd8e/mcp_types-2.3.0.tar.gz", hash = "sha256:d1e46549edb35ee19a94940fcee6d1addd7e589ab7ea92dda83f5d84781fc362", size = 65898, upload-time = "2026-10-02T22:06:57.863Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/48/32583066b155bb5d4507e7887dc88ee23118cc4de8e2a12870c49719e2a8/mcp_types-2.3.0-py3-none-any.whl", hash = "sha256:968efdbdaedfab06adae40d378a34395f1090c5921d4be3c9cde283aaf76d91d", size = 69108, upload-time = "2026-10-02T22:06:54.099Z" }, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -537,6 +545,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] +[[package]] +name = "opentelemetry-api" +version = "1.45.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + [[package]] name = "packaging" version = "24.2" @@ -697,20 +717,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, ] -[[package]] -name = "pydantic-settings" -version = "2.14.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5c/b5/8f48e906c3e0205276e8bd8cb7512217a87b2685304d64be27cad5b3019f/pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f", size = 237700, upload-time = "2026-06-19T13:44:56.324Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/c1/6e422f34e569cf8e18df68d1939c81c099d2b61e4f7d9621c8a77560799c/pydantic_settings-2.14.2-py3-none-any.whl", hash = "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440", size = 61715, upload-time = "2026-06-19T13:44:55.02Z" }, -] - [[package]] name = "pygments" version = "2.20.0" @@ -808,15 +814,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] -[[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.32" @@ -1018,27 +1015,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053, upload-time = "2021-05-05T14:18:17.237Z" }, ] -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, -] - [[package]] name = "sse-starlette" -version = "2.1.3" +version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "starlette" }, - { name = "uvicorn" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678, upload-time = "2024-08-01T08:52:50.248Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/be/0123026f719d1a7936f214a88b553bb5701e04ff2511147c1dab0c5035eb/sse_starlette-3.5.0.tar.gz", hash = "sha256:75de713aa8a9441513cc283220826da079d982770965b951e9437720e8bafdb2", size = 36794, upload-time = "2026-09-28T17:48:14.7Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/aa/36b271bc4fa1d2796311ee7c7283a3a1c348bad426d37293609ca4300eef/sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772", size = 9383, upload-time = "2024-08-01T08:52:48.659Z" }, + { url = "https://files.pythonhosted.org/packages/be/e4/cdda14023c316d71493bc54fdffc3dd006631b88866145c9d3cc33e0f1df/sse_starlette-3.5.0-py3-none-any.whl", hash = "sha256:3e6e1070df3f0f5d9cea81496de92dbb72f6721871d99748ece67441dd8b7997", size = 17407, upload-time = "2026-09-28T17:48:13.228Z" }, ] [[package]] @@ -1093,13 +1080,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] +[[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" +version = "4.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] [[package]] From b5af22668592700c02236d737f9836c42654edb6 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 12:24:56 -0400 Subject: [PATCH 02/65] chore: migrate the TypeScript servers to TS SDK v2 (#4856) Run @modelcontextprotocol/codemod v1-to-v2 (2.3.1) on everything, filesystem, memory and sequentialthinking, and finish what it leaves: - Manifests: drop @modelcontextprotocol/sdk; depend on @modelcontextprotocol/server (plus node and server-legacy in everything), with client (and core, for everything's skipped task tests) as devDependencies; zod ^4.2.0; engines node >=20. The root devDependency for the boot smoke moves to @modelcontextprotocol/client. - everything: wrap the three raw-shape prompt argsSchemas in z.object(); drop the result schemas from ctx.mcpReq.send(); keep HTTP+SSE on the frozen server-legacy bridge. - Experimental tasks are gone in v2 (SEP-2663): remove simulate-research-query and the two trigger-*-async tools and the tasks capability, pending #4852. Their tests stay, skipped, behind typed stand-ins for the removed v1 surface. - Re-baseline the Part 1 (#4854) assertions that SDK v2's documented wire changes move: unknown tools reject with -32602, the "MCP error : " prefix is gone, validation issues read "field: message", tool schemas declare JSON Schema 2020-12, tools no longer advertise execution.taskSupport, and the resource-not-found message reads "Resource not found: ". - Docs, skills and the smoke script follow; one major changeset per server records the breaking changes. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .changeset/everything-sdk-v2.md | 5 + .changeset/filesystem-sdk-v2.md | 5 + .changeset/memory-sdk-v2.md | 5 + .changeset/sequentialthinking-sdk-v2.md | 5 + .claude/skills/security-advisory/SKILL.md | 3 +- .claude/skills/testing/SKILL.md | 2 +- package-lock.json | 253 +++++++------ package.json | 2 +- scripts/smoke-servers.mjs | 12 +- src/everything/__tests__/harness.ts | 13 +- .../__tests__/in-memory-event-store.test.ts | 2 +- .../__tests__/registrations.test.ts | 44 +-- src/everything/__tests__/resources.test.ts | 2 +- src/everything/__tests__/roots.test.ts | 9 +- .../__tests__/sampling-elicitation.test.ts | 42 +-- .../__tests__/server-version.test.ts | 3 +- src/everything/__tests__/server.test.ts | 5 +- src/everything/__tests__/sse.test.ts | 3 +- src/everything/__tests__/stdio.test.ts | 4 +- .../__tests__/streamable-http.test.ts | 16 +- src/everything/__tests__/surface.test.ts | 218 +++-------- src/everything/__tests__/tasks.test.ts | 175 ++++++--- src/everything/__tests__/tools.test.ts | 14 +- src/everything/docs/features.md | 56 +-- src/everything/docs/instructions.md | 2 - src/everything/docs/structure.md | 11 +- src/everything/package.json | 11 +- src/everything/prompts/args.ts | 6 +- src/everything/prompts/completions.ts | 7 +- src/everything/prompts/index.ts | 2 +- src/everything/prompts/resource.ts | 7 +- src/everything/prompts/simple.ts | 2 +- src/everything/resources/files.ts | 2 +- src/everything/resources/index.ts | 2 +- src/everything/resources/session.ts | 5 +- src/everything/resources/subscriptions.ts | 18 +- src/everything/resources/templates.ts | 4 +- src/everything/server/index.ts | 26 +- src/everything/server/logging.ts | 3 +- src/everything/server/roots.ts | 10 +- src/everything/tools/echo.ts | 3 +- src/everything/tools/get-annotated-message.ts | 3 +- src/everything/tools/get-env.ts | 3 +- src/everything/tools/get-resource-links.ts | 3 +- .../tools/get-resource-reference.ts | 3 +- src/everything/tools/get-roots-list.ts | 7 +- .../tools/get-structured-content.ts | 4 +- src/everything/tools/get-sum.ts | 3 +- src/everything/tools/get-tiny-image.ts | 3 +- src/everything/tools/gzip-file-as-resource.ts | 7 +- src/everything/tools/index.ts | 14 +- .../tools/simulate-research-query.ts | 348 ------------------ .../tools/toggle-simulated-logging.ts | 7 +- .../tools/toggle-subscriber-updates.ts | 7 +- .../trigger-elicitation-request-async.ts | 295 --------------- .../tools/trigger-elicitation-request.ts | 11 +- .../tools/trigger-long-running-operation.ts | 9 +- .../tools/trigger-sampling-request-async.ts | 236 ------------ .../tools/trigger-sampling-request.ts | 12 +- .../tools/trigger-url-elicitation.ts | 12 +- .../transports/in-memory-event-store.ts | 4 +- src/everything/transports/sse.ts | 2 +- src/everything/transports/stdio.ts | 2 +- src/everything/transports/streamableHttp.ts | 12 +- src/filesystem/__tests__/bin-smoke.test.ts | 4 +- src/filesystem/__tests__/helpers.ts | 16 +- src/filesystem/__tests__/server-paths.test.ts | 2 +- src/filesystem/__tests__/server-roots.test.ts | 6 +- src/filesystem/__tests__/server-tools.test.ts | 25 +- src/filesystem/__tests__/stdio.test.ts | 6 +- src/filesystem/index.ts | 4 +- src/filesystem/package.json | 8 +- src/filesystem/roots-utils.ts | 2 +- src/filesystem/server.ts | 17 +- src/memory/__tests__/entry-point.test.ts | 3 +- src/memory/__tests__/helpers.ts | 7 +- src/memory/__tests__/resource.test.ts | 8 +- src/memory/__tests__/stdio-smoke.test.ts | 4 +- src/memory/__tests__/tools.test.ts | 37 +- src/memory/index.ts | 85 ++--- src/memory/package.json | 8 +- .../__tests__/entry-point.test.ts | 3 +- src/sequentialthinking/__tests__/helpers.ts | 3 +- .../__tests__/input-schema.test.ts | 26 +- .../__tests__/stdio-smoke.test.ts | 4 +- .../__tests__/tools-list.test.ts | 43 ++- src/sequentialthinking/index.ts | 14 +- src/sequentialthinking/package.json | 8 +- 88 files changed, 674 insertions(+), 1695 deletions(-) create mode 100644 .changeset/everything-sdk-v2.md create mode 100644 .changeset/filesystem-sdk-v2.md create mode 100644 .changeset/memory-sdk-v2.md create mode 100644 .changeset/sequentialthinking-sdk-v2.md delete mode 100644 src/everything/tools/simulate-research-query.ts delete mode 100644 src/everything/tools/trigger-elicitation-request-async.ts delete mode 100644 src/everything/tools/trigger-sampling-request-async.ts diff --git a/.changeset/everything-sdk-v2.md b/.changeset/everything-sdk-v2.md new file mode 100644 index 0000000000..39558eefc6 --- /dev/null +++ b/.changeset/everything-sdk-v2.md @@ -0,0 +1,5 @@ +--- +"@modelcontextprotocol/server-everything": major +--- + +Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`, `@modelcontextprotocol/node`, and `@modelcontextprotocol/server-legacy` for the deprecated HTTP+SSE transport) from `@modelcontextprotocol/sdk` 1.x (#4856). The task tools `simulate-research-query`, `trigger-sampling-request-async` and `trigger-elicitation-request-async` are removed, and the server no longer advertises the `tasks` capability, because SDK v2 dropped the experimental tasks layer they were built on; they return on the tasks extension in #4852. Breaking for clients: the server now requires Node.js 20 or later; an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. The optional `state` argument of `args-prompt` now carries its description. diff --git a/.changeset/filesystem-sdk-v2.md b/.changeset/filesystem-sdk-v2.md new file mode 100644 index 0000000000..c39ccdf732 --- /dev/null +++ b/.changeset/filesystem-sdk-v2.md @@ -0,0 +1,5 @@ +--- +"@modelcontextprotocol/server-filesystem": major +--- + +Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x (#4856). Breaking for clients: the server now requires Node.js 20 or later; an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); and tools no longer advertise `execution.taskSupport`. diff --git a/.changeset/memory-sdk-v2.md b/.changeset/memory-sdk-v2.md new file mode 100644 index 0000000000..9d0b41c03b --- /dev/null +++ b/.changeset/memory-sdk-v2.md @@ -0,0 +1,5 @@ +--- +"@modelcontextprotocol/server-memory": major +--- + +Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x (#4856). Breaking for clients: the server now requires Node.js 20 or later; an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. diff --git a/.changeset/sequentialthinking-sdk-v2.md b/.changeset/sequentialthinking-sdk-v2.md new file mode 100644 index 0000000000..4965463dd1 --- /dev/null +++ b/.changeset/sequentialthinking-sdk-v2.md @@ -0,0 +1,5 @@ +--- +"@modelcontextprotocol/server-sequential-thinking": major +--- + +Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x (#4856). Breaking for clients: the server now requires Node.js 20 or later; an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. diff --git a/.claude/skills/security-advisory/SKILL.md b/.claude/skills/security-advisory/SKILL.md index e2b85e39c6..841f6bc476 100644 --- a/.claude/skills/security-advisory/SKILL.md +++ b/.claude/skills/security-advisory/SKILL.md @@ -140,7 +140,8 @@ this order: Servers listed in the README or the Registry are third-party; their reports go to their own maintainers. 2. **Is the code path the server's, or an SDK's?** The TypeScript servers are - built on `@modelcontextprotocol/sdk` + built on the TypeScript SDK's v2 packages (`@modelcontextprotocol/server`, + `@modelcontextprotocol/node` and the frozen `@modelcontextprotocol/server-legacy`) ([typescript-sdk](https://github.com/modelcontextprotocol/typescript-sdk)), the Python servers on `mcp` ([python-sdk](https://github.com/modelcontextprotocol/python-sdk)). Transport diff --git a/.claude/skills/testing/SKILL.md b/.claude/skills/testing/SKILL.md index 7ba7883001..bfaecb72d8 100644 --- a/.claude/skills/testing/SKILL.md +++ b/.claude/skills/testing/SKILL.md @@ -83,7 +83,7 @@ Each `connect` returns the `client` and a `close()`; call `close()` in ```ts import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import type { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import type { Client } from "@modelcontextprotocol/client"; import { connect, contentOf, textOf, type Session } from "./harness.js"; describe("echo, over the protocol", () => { diff --git a/package-lock.json b/package-lock.json index 6cdfdc7427..f9b270efc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "@changesets/changelog-github": "^1.0.1", "@changesets/cli": "^3.0.3", "@eslint/js": "^10.0.1", - "@modelcontextprotocol/sdk": "^1.30.0", + "@modelcontextprotocol/client": "^2.3.1", "eslint": "^10.11.0", "globals": "^17.12.0", "prettier": "3.8.4", @@ -576,18 +576,6 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@hono/node-server": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.12.tgz", - "integrity": "sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "hono": "^4" - } - }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -748,44 +736,76 @@ "node": ">=20.0.0" } }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", - "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", - "license": "MIT", + "node_modules/@modelcontextprotocol/client": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.3.1.tgz", + "integrity": "sha512-mIGZXpHsjnZ6lD+gD/WCMpR5k8yVQQ8nNFH1N0Srf7AvnwTUMYD6pvTY8ng2+He5FfV7YMZLmrjL7cLa/cc3dQ==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@hono/node-server": "^1.19.9 || ^2.0.5", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", + "@modelcontextprotocol/core": "2.3.1", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", + "eventsource-parser": "^3.0.8", "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" + "zod": "^4.2.0" }, "engines": { - "node": ">=18" + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/core": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.3.1.tgz", + "integrity": "sha512-laVmIhPGpWi7rG9q+0UigblRN0Hws/Yjlb6qLFtVqryXVLkc4h+yN8z731POD2LnAGQlFe7/tXhUm7Bi0YB91g==", + "license": "Apache-2.0", + "dependencies": { + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/node/-/node-2.1.1.tgz", + "integrity": "sha512-EY92OAXN2xYL7tNS/MVsWJW1v8VHAPwl9B4Ca3pCrjmvqyC+S1C/6UHsMxF2GoFPraS5Yyg56vFz9brVpFojMA==", + "license": "Apache-2.0", + "dependencies": { + "@hono/node-server": "^1.19.9", + "hono": "^4.11.4" + }, + "engines": { + "node": ">=20" }, "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" + "@modelcontextprotocol/server": "^2.3.0" + } + }, + "node_modules/@modelcontextprotocol/node/node_modules/@hono/node-server": { + "version": "1.19.17", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.17.tgz", + "integrity": "sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@modelcontextprotocol/server": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.3.1.tgz", + "integrity": "sha512-e59MfWuSssj6DoQ96JCTqdW1AXT5/XtnTl0WWd6MU1Ka434fFUGGosCLJbGNySDhHGvxQWoRWK137FXYFkYNWw==", + "license": "Apache-2.0", + "dependencies": { + "@modelcontextprotocol/core": "2.3.1", + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" } }, "node_modules/@modelcontextprotocol/server-everything": { @@ -796,6 +816,33 @@ "resolved": "src/filesystem", "link": true }, + "node_modules/@modelcontextprotocol/server-legacy": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server-legacy/-/server-legacy-2.3.1.tgz", + "integrity": "sha512-+cuB+hl7s8DiFXIt13le3cD2wZPOeDYwlycAkRMPljqqNbOLekGRiyslx8WXfqaXSCveS1eDD8/B8rmxz9BZFQ==", + "deprecated": "This package is a frozen copy of v1's SSE transport and OAuth Authorization Server helpers for migration purposes only. Use StreamableHTTP from @modelcontextprotocol/server and a dedicated OAuth server in production. Will not receive new features.", + "license": "Apache-2.0", + "dependencies": { + "@modelcontextprotocol/core": "2.3.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "express-rate-limit": "^8.2.1", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "express": "^4.18.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "express": { + "optional": true + } + } + }, "node_modules/@modelcontextprotocol/server-memory": { "resolved": "src/memory", "link": true @@ -1754,39 +1801,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -2143,6 +2157,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2531,6 +2546,7 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, "license": "MIT", "dependencies": { "eventsource-parser": "^3.0.1" @@ -2543,6 +2559,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "dev": true, "license": "MIT", "engines": { "node": ">=18.0.0" @@ -2715,6 +2732,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -2765,22 +2783,6 @@ "fast-string-truncated-width": "^3.0.2" } }, - "node_modules/fast-uri": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", - "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/fast-wrap-ansi": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", @@ -3333,6 +3335,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -3385,6 +3388,7 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -3397,18 +3401,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "license": "BSD-2-Clause" - }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -4152,6 +4144,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4449,15 +4442,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/resolve": { "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", @@ -4661,6 +4645,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -4673,6 +4658,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5280,6 +5266,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -5439,36 +5426,34 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - }, "src/everything": { "name": "@modelcontextprotocol/server-everything", "version": "1.0.0", "license": "SEE LICENSE IN LICENSE", "dependencies": { - "@modelcontextprotocol/sdk": "^1.30.0", + "@modelcontextprotocol/node": "^2.1.1", + "@modelcontextprotocol/server": "^2.3.1", + "@modelcontextprotocol/server-legacy": "^2.3.1", "cors": "^2.8.5", "express": "^5.2.1", "jszip": "^3.10.1", - "zod": "^4.0.0" + "zod": "^4.2.0" }, "bin": { "mcp-server-everything": "dist/index.js" }, "devDependencies": { + "@modelcontextprotocol/client": "^2.3.1", + "@modelcontextprotocol/core": "^2.3.1", "@types/cors": "^2.8.19", "@types/express": "^5.0.6", "@vitest/coverage-v8": "^4.1.8", "shx": "^0.4.0", "typescript": "^5.8.2", "vitest": "^4.1.8" + }, + "engines": { + "node": ">=20" } }, "src/filesystem": { @@ -5476,16 +5461,17 @@ "version": "1.0.0", "license": "SEE LICENSE IN LICENSE", "dependencies": { - "@modelcontextprotocol/sdk": "^1.30.0", + "@modelcontextprotocol/server": "^2.3.1", "diff": "^8.0.3", "glob": "^13.0.6", "minimatch": "^10.0.1", - "zod": "^4.0.0" + "zod": "^4.2.0" }, "bin": { "mcp-server-filesystem": "dist/index.js" }, "devDependencies": { + "@modelcontextprotocol/client": "^2.3.1", "@types/diff": "^5.0.9", "@types/minimatch": "^5.1.2", "@types/node": "^22", @@ -5493,6 +5479,9 @@ "shx": "^0.4.0", "typescript": "^5.8.2", "vitest": "^4.1.8" + }, + "engines": { + "node": ">=20" } }, "src/memory": { @@ -5500,18 +5489,22 @@ "version": "1.0.0", "license": "SEE LICENSE IN LICENSE", "dependencies": { - "@modelcontextprotocol/sdk": "^1.30.0", - "zod": "^4.0.0" + "@modelcontextprotocol/server": "^2.3.1", + "zod": "^4.2.0" }, "bin": { "mcp-server-memory": "dist/index.js" }, "devDependencies": { + "@modelcontextprotocol/client": "^2.3.1", "@types/node": "^22", "@vitest/coverage-v8": "^4.1.8", "shx": "^0.4.0", "typescript": "^5.8.2", "vitest": "^4.1.8" + }, + "engines": { + "node": ">=20" } }, "src/sequentialthinking": { @@ -5519,21 +5512,25 @@ "version": "1.0.0", "license": "SEE LICENSE IN LICENSE", "dependencies": { - "@modelcontextprotocol/sdk": "^1.30.0", + "@modelcontextprotocol/server": "^2.3.1", "chalk": "^5.3.0", "yargs": "^17.7.2", - "zod": "^4.0.0" + "zod": "^4.2.0" }, "bin": { "mcp-server-sequential-thinking": "dist/index.js" }, "devDependencies": { + "@modelcontextprotocol/client": "^2.3.1", "@types/node": "^22", "@types/yargs": "^17.0.32", "@vitest/coverage-v8": "^4.1.8", "shx": "^0.4.0", "typescript": "^5.8.2", "vitest": "^4.1.8" + }, + "engines": { + "node": ">=20" } } } diff --git a/package.json b/package.json index 44af657abf..4db728718f 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "@changesets/changelog-github": "^1.0.1", "@changesets/cli": "^3.0.3", "@eslint/js": "^10.0.1", - "@modelcontextprotocol/sdk": "^1.30.0", + "@modelcontextprotocol/client": "^2.3.1", "eslint": "^10.11.0", "globals": "^17.12.0", "prettier": "3.8.4", diff --git a/scripts/smoke-servers.mjs b/scripts/smoke-servers.mjs index ed21b3f0ff..f0e98f67d5 100755 --- a/scripts/smoke-servers.mjs +++ b/scripts/smoke-servers.mjs @@ -62,10 +62,12 @@ import { tmpdir } from "node:os"; import path from "node:path"; import { setTimeout as delay } from "node:timers/promises"; import { fileURLToPath, pathToFileURL } from "node:url"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; -import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; -import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; +import { + Client, + SSEClientTransport, + StreamableHTTPClientTransport, +} from "@modelcontextprotocol/client"; +import { StdioClientTransport } from "@modelcontextprotocol/client/stdio"; const repoRoot = path.resolve( path.dirname(fileURLToPath(import.meta.url)), @@ -310,7 +312,7 @@ async function exercise(client, server, ctx) { throw new Error( `tools/list does not include \`${call.name}\` (got: ${tools.map((t) => t.name).join(", ")})`, ); - const result = await client.callTool(call, undefined, options); + const result = await client.callTool(call, options); const text = textOf(result); if (result.isError) throw new Error(`\`${call.name}\` returned an error result: ${text}`); diff --git a/src/everything/__tests__/harness.ts b/src/everything/__tests__/harness.ts index b87cbd39fe..326058244b 100644 --- a/src/everything/__tests__/harness.ts +++ b/src/everything/__tests__/harness.ts @@ -17,22 +17,18 @@ * runs the server's `cleanup(sessionId)` the way the HTTP transports do. */ import { randomUUID } from "node:crypto"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; -import type { TaskStore } from "@modelcontextprotocol/sdk/experimental/tasks"; +import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import type { ClientCapabilities, ContentBlock, ServerNotification, -} from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/client"; import { createServer } from "../server/index.js"; /** Options for one in-process session. */ export type ConnectOptions = { /** The capabilities the client declares in `initialize`. */ capabilities?: ClientCapabilities; - /** A client-side task store, for servers that send task-augmented requests. */ - taskStore?: TaskStore; /** Install request handlers on the client before it connects. */ setup?: (client: Client) => void; /** The server transport's session id; `null` leaves it undefined. */ @@ -62,10 +58,7 @@ export async function connect(options: ConnectOptions = {}): Promise { const client = new Client( { name: "everything-test-client", version: "0.0.0" }, - { - capabilities: options.capabilities ?? {}, - ...(options.taskStore ? { taskStore: options.taskStore } : {}), - }, + { capabilities: options.capabilities ?? {} }, ); const notifications: ServerNotification[] = []; client.fallbackNotificationHandler = async (notification) => { diff --git a/src/everything/__tests__/in-memory-event-store.test.ts b/src/everything/__tests__/in-memory-event-store.test.ts index 6d59c296a7..1f65ac7be1 100644 --- a/src/everything/__tests__/in-memory-event-store.test.ts +++ b/src/everything/__tests__/in-memory-event-store.test.ts @@ -5,7 +5,7 @@ // unknown id before replaying). import { describe, it, expect } from "vitest"; -import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js"; +import type { JSONRPCMessage } from "@modelcontextprotocol/server"; import { InMemoryEventStore } from "../transports/in-memory-event-store.js"; const message = (method: string): JSONRPCMessage => ({ diff --git a/src/everything/__tests__/registrations.test.ts b/src/everything/__tests__/registrations.test.ts index de73827f3a..f828ce4ffa 100644 --- a/src/everything/__tests__/registrations.test.ts +++ b/src/everything/__tests__/registrations.test.ts @@ -3,7 +3,7 @@ // imports they replace ran inside the 5 s test timeout, and a cold start // (transforming the whole tool tree) could take longer than that. import { describe, it, expect, vi } from "vitest"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from "@modelcontextprotocol/server"; import { registerConditionalTools, registerTools } from "../tools/index.js"; import { registerPrompts } from "../prompts/index.js"; import { readInstructions, registerResources } from "../resources/index.js"; @@ -52,7 +52,7 @@ describe("Registration Index Files", () => { }); it("should register conditional tools based on capabilities", () => { - // Server with all capabilities including experimental tasks API + // Server with all capabilities const mockServerWithCapabilities = { registerTool: vi.fn(), server: { @@ -62,18 +62,13 @@ describe("Registration Index Files", () => { sampling: {}, })), }, - experimental: { - tasks: { - registerToolTask: vi.fn(), - }, - }, } as unknown as McpServer; registerConditionalTools(mockServerWithCapabilities); // Should register 4 conditional tools via registerTool when all capabilities - // are present. Task-based tools register via registerToolTask (counted separately), - // so they are not included in this registerTool count. + // are present. The task tools went with SDK v2's experimental tasks layer; + // Part 5 (#4852) brings them back. expect(mockServerWithCapabilities.registerTool).toHaveBeenCalledTimes(4); const registeredTools = vi @@ -83,11 +78,6 @@ describe("Registration Index Files", () => { expect(registeredTools).toContain("trigger-elicitation-request"); expect(registeredTools).toContain("trigger-url-elicitation"); expect(registeredTools).toContain("trigger-sampling-request"); - - // Task-based tools are registered via experimental.tasks.registerToolTask - expect( - mockServerWithCapabilities.experimental.tasks.registerToolTask, - ).toHaveBeenCalled(); }); it("should not register conditional tools before capabilities are known", () => { @@ -98,11 +88,6 @@ describe("Registration Index Files", () => { server: { getClientCapabilities: vi.fn(() => undefined), }, - experimental: { - tasks: { - registerToolTask: vi.fn(), - }, - }, } as unknown as McpServer; // partial mock: McpServer's private members rule out a structural literal registerConditionalTools(mockServerBeforeInit); @@ -116,11 +101,6 @@ describe("Registration Index Files", () => { server: { getClientCapabilities: vi.fn(() => ({})), }, - experimental: { - tasks: { - registerToolTask: vi.fn(), - }, - }, } as unknown as McpServer; registerConditionalTools(mockServerNoCapabilities); @@ -140,11 +120,6 @@ describe("Registration Index Files", () => { tasks: { requests: { elicitation: { create: {} } } }, })), }, - experimental: { - tasks: { - registerToolTask: vi.fn(), - }, - }, } as unknown as McpServer; // partial mock: McpServer's private members rule out a structural literal registerConditionalTools(mockServerUrlOnly); @@ -202,22 +177,13 @@ describe("Registration Index Files", () => { server: { getClientCapabilities: vi.fn(() => capabilities), }, - experimental: { - tasks: { - registerToolTask: vi.fn(), - }, - }, } as unknown as McpServer; // partial mock: McpServer's private members rule out a structural literal registerConditionalTools(mockServer); - const viaRegisterTool = vi + return vi .mocked(mockServer.registerTool) .mock.calls.map((call) => call[0]); - const viaRegisterToolTask = vi - .mocked(mockServer.experimental.tasks.registerToolTask) - .mock.calls.map((call) => call[0]); - return [...viaRegisterTool, ...viaRegisterToolTask]; }; // A tool is capability-gated if declaring the capabilities makes it appear. diff --git a/src/everything/__tests__/resources.test.ts b/src/everything/__tests__/resources.test.ts index 6fdabcba4b..4e01f2e0bb 100644 --- a/src/everything/__tests__/resources.test.ts +++ b/src/everything/__tests__/resources.test.ts @@ -84,7 +84,7 @@ describe("dynamic resource templates", () => { it("rejects a URI that matches no resource or template", async () => { await expect( session.client.readResource({ uri: "demo://resource/nowhere" }), - ).rejects.toThrow("Resource demo://resource/nowhere not found"); + ).rejects.toThrow("Resource not found: demo://resource/nowhere"); }); }); diff --git a/src/everything/__tests__/roots.test.ts b/src/everything/__tests__/roots.test.ts index 9c98aba1b2..88cada7c13 100644 --- a/src/everything/__tests__/roots.test.ts +++ b/src/everything/__tests__/roots.test.ts @@ -6,10 +6,7 @@ * all driven by a test client that answers the server's `roots/list`. */ import { afterEach, describe, expect, it, vi } from "vitest"; -import { - ListRootsRequestSchema, - type Root, -} from "@modelcontextprotocol/sdk/types.js"; +import type { Root } from "@modelcontextprotocol/server"; import { connect, contentOf, @@ -32,7 +29,7 @@ async function connectWithRoots(answer: () => Root[]) { const s = await connect({ capabilities: { roots: { listChanged: true } }, setup: (client) => - client.setRequestHandler(ListRootsRequestSchema, async () => { + client.setRequestHandler("roots/list", async () => { requests++; return { roots: answer() }; }), @@ -110,7 +107,7 @@ describe("roots sync after initialize", () => { session = s; await vi.waitFor(() => expect(errorSpy).toHaveBeenCalledWith( - `Failed to request roots from client ${s.sessionId}: MCP error -32603: no roots for you`, + `Failed to request roots from client ${s.sessionId}: no roots for you`, ), ); expect(logData(s)).toEqual([]); diff --git a/src/everything/__tests__/sampling-elicitation.test.ts b/src/everything/__tests__/sampling-elicitation.test.ts index bc5e36f8b7..4acbc272e1 100644 --- a/src/everything/__tests__/sampling-elicitation.test.ts +++ b/src/everything/__tests__/sampling-elicitation.test.ts @@ -7,16 +7,13 @@ * server asked and what the tool then returned. */ import { afterEach, describe, expect, it } from "vitest"; -import { - CreateMessageRequestSchema, - ElicitRequestSchema, - ErrorCode, - McpError, - type ClientCapabilities, - type CreateMessageRequest, - type ElicitRequest, - type ElicitResult, -} from "@modelcontextprotocol/sdk/types.js"; +import { ProtocolError, ProtocolErrorCode } from "@modelcontextprotocol/server"; +import type { + ClientCapabilities, + CreateMessageRequest, + ElicitRequest, + ElicitResult, +} from "@modelcontextprotocol/server"; import { __resetIssuedErrorPathElicitations } from "../tools/trigger-url-elicitation.js"; import { connect, @@ -40,7 +37,7 @@ describe("trigger-sampling-request", () => { session = await connect({ capabilities: { sampling: {} }, setup: (client) => - client.setRequestHandler(CreateMessageRequestSchema, async (req) => { + client.setRequestHandler("sampling/createMessage", async (req) => { asked.push(req.params); return { role: "assistant", @@ -91,7 +88,7 @@ describe("trigger-sampling-request", () => { session = await connect({ capabilities: { sampling: {} }, setup: (client) => - client.setRequestHandler(CreateMessageRequestSchema, async (req) => { + client.setRequestHandler("sampling/createMessage", async (req) => { maxTokens = req.params.maxTokens; return { role: "assistant", @@ -111,8 +108,11 @@ describe("trigger-sampling-request", () => { session = await connect({ capabilities: { sampling: {} }, setup: (client) => - client.setRequestHandler(CreateMessageRequestSchema, async () => { - throw new McpError(ErrorCode.InvalidRequest, "user rejected"); + client.setRequestHandler("sampling/createMessage", async () => { + throw new ProtocolError( + ProtocolErrorCode.InvalidRequest, + "user rejected", + ); }), }); const result = await session.client.callTool({ @@ -125,7 +125,7 @@ describe("trigger-sampling-request", () => { content: [ { type: "text", - text: "MCP error -32600: MCP error -32600: user rejected", + text: "user rejected", }, ], }); @@ -143,7 +143,7 @@ async function connectElicitation( ...options, capabilities, setup: (client) => - client.setRequestHandler(ElicitRequestSchema, async (req) => { + client.setRequestHandler("elicitation/create", async (req) => { asked.push(req.params); return answer(req); }), @@ -476,12 +476,12 @@ describe("trigger-url-elicitation", () => { const { s, asked } = await connectElicitation(() => ({ action: "accept" })); session = s; const error = await errorPathRejection(s); - expect(error).toBeInstanceOf(McpError); - const mcpError = error as McpError; + expect(error).toBeInstanceOf(ProtocolError); + const mcpError = error as ProtocolError; expect(mcpError.code).toBe(-32042); // The SDK prefixes the code twice on the client side. expect(mcpError.message).toBe( - "MCP error -32042: MCP error -32042: This request requires browser-based authorization.", + "This request requires browser-based authorization.", ); expect(mcpError.data).toEqual({ elicitations: [ @@ -510,7 +510,7 @@ describe("trigger-url-elicitation", () => { // The one-shot marker was consumed, so a third call errors again. expect(await errorPathRejection(s, { elicitationId: "x" })).toBeInstanceOf( - McpError, + ProtocolError, ); }); @@ -521,7 +521,7 @@ describe("trigger-url-elicitation", () => { // A different requested id is a different call: it errors. expect( await errorPathRejection(s, { elicitationId: "other" }), - ).toBeInstanceOf(McpError); + ).toBeInstanceOf(ProtocolError); // The original call (no requested id) is recognized as the retry. const retry = await callUrlTool(s, { errorPath: true }); expect(retry.isError).toBeUndefined(); diff --git a/src/everything/__tests__/server-version.test.ts b/src/everything/__tests__/server-version.test.ts index 579b42897d..4335b8c524 100644 --- a/src/everything/__tests__/server-version.test.ts +++ b/src/everything/__tests__/server-version.test.ts @@ -5,8 +5,7 @@ import { describe, it, expect, vi } from "vitest"; import { createRequire } from "node:module"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; +import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import { createServer } from "../server/index.js"; import { resolvePackageVersion, SERVER_VERSION } from "../version.js"; diff --git a/src/everything/__tests__/server.test.ts b/src/everything/__tests__/server.test.ts index 2135944a4f..a864088ae6 100644 --- a/src/everything/__tests__/server.test.ts +++ b/src/everything/__tests__/server.test.ts @@ -6,9 +6,8 @@ * for a server with no initialized client. */ import { describe, expect, it } from "vitest"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { InMemoryTransport, McpServer } from "@modelcontextprotocol/server"; +import { Client } from "@modelcontextprotocol/client"; import { createServer } from "../server/index.js"; import { registerSessionResource } from "../resources/session.js"; import { syncRoots } from "../server/roots.js"; diff --git a/src/everything/__tests__/sse.test.ts b/src/everything/__tests__/sse.test.ts index 62c7c4f12f..886cd30c66 100644 --- a/src/everything/__tests__/sse.test.ts +++ b/src/everything/__tests__/sse.test.ts @@ -8,8 +8,7 @@ import type { Server } from "node:http"; import { once } from "node:events"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; +import { Client, SSEClientTransport } from "@modelcontextprotocol/client"; import { createApp } from "../transports/sse.js"; let http: Server; diff --git a/src/everything/__tests__/stdio.test.ts b/src/everything/__tests__/stdio.test.ts index a5ff839568..b1b9847d2c 100644 --- a/src/everything/__tests__/stdio.test.ts +++ b/src/everything/__tests__/stdio.test.ts @@ -7,8 +7,8 @@ */ import { PassThrough } from "node:stream"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { Client } from "@modelcontextprotocol/client"; import { main, startStdioServer, diff --git a/src/everything/__tests__/streamable-http.test.ts b/src/everything/__tests__/streamable-http.test.ts index d374bfac3a..fe735c97b9 100644 --- a/src/everything/__tests__/streamable-http.test.ts +++ b/src/everything/__tests__/streamable-http.test.ts @@ -11,9 +11,11 @@ import type { Server } from "node:http"; import { once } from "node:events"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import { + Client, + StreamableHTTPClientTransport, +} from "@modelcontextprotocol/client"; import { createApp, type StreamableHttpApp, @@ -328,7 +330,7 @@ describe("Streamable HTTP transport", () => { it("maps a POST that throws before answering to a JSON-RPC internal error", async () => { vi.spyOn( - StreamableHTTPServerTransport.prototype, + NodeStreamableHTTPServerTransport.prototype, "handleRequest", ).mockRejectedValueOnce(new Error("boom")); const response = await post({ jsonrpc: "2.0", id: 1, method: "ping" }); @@ -346,7 +348,7 @@ describe("Streamable HTTP transport", () => { it("leaves a POST alone when it throws after answering", async () => { const { sessionId } = await rawInitialize(); vi.spyOn( - StreamableHTTPServerTransport.prototype, + NodeStreamableHTTPServerTransport.prototype, "handleRequest", ).mockImplementationOnce(async (_req, res) => { res.writeHead(200).end("partial"); @@ -363,7 +365,7 @@ describe("Streamable HTTP transport", () => { it("maps a DELETE that throws to a JSON-RPC internal error, unless it already answered", async () => { const { sessionId } = await rawInitialize(); const handle = vi.spyOn( - StreamableHTTPServerTransport.prototype, + NodeStreamableHTTPServerTransport.prototype, "handleRequest", ); handle.mockRejectedValueOnce(new Error("boom")); @@ -444,7 +446,7 @@ describe("shutdown", () => { const { client, sessionId } = await connectHttp(); const failure = new Error("close failed"); vi.spyOn( - StreamableHTTPServerTransport.prototype, + NodeStreamableHTTPServerTransport.prototype, "close", ).mockRejectedValueOnce(failure); await app.shutdown(); diff --git a/src/everything/__tests__/surface.test.ts b/src/everything/__tests__/surface.test.ts index 0f589341d8..4086b323f2 100644 --- a/src/everything/__tests__/surface.test.ts +++ b/src/everything/__tests__/surface.test.ts @@ -31,7 +31,6 @@ const BASE_TOOLS = [ "get-sum", "get-tiny-image", "gzip-file-as-resource", - "simulate-research-query", "toggle-simulated-logging", "toggle-subscriber-updates", "trigger-long-running-operation", @@ -52,11 +51,6 @@ describe("initialize", () => { resources: { subscribe: true, listChanged: true }, logging: {}, completions: {}, - tasks: { - list: {}, - cancel: {}, - requests: { tools: { call: {} } }, - }, }); expect(client.getInstructions()).toMatch( /^# Everything Server – Server Instructions/, @@ -75,9 +69,7 @@ describe("initialize", () => { const gated = [ "get-roots-list", "trigger-elicitation-request", - "trigger-elicitation-request-async", "trigger-sampling-request", - "trigger-sampling-request-async", "trigger-url-elicitation", ]; @@ -116,9 +108,7 @@ describe("tools/list", () => { ...BASE_TOOLS, "get-roots-list", "trigger-elicitation-request", - "trigger-elicitation-request-async", "trigger-sampling-request", - "trigger-sampling-request-async", "trigger-url-elicitation", ].sort(), ); @@ -135,6 +125,20 @@ describe("tools/list", () => { { elicitation: { form: {}, url: {} } }, ["trigger-elicitation-request", "trigger-url-elicitation"], ], + ])( + "gates tools on the client's declared capabilities: %j", + async (capabilities, extra) => { + session = await connect({ capabilities }); + const { tools } = await session.client.listTools(); + expect(tools.map((t) => t.name).sort()).toEqual( + [...BASE_TOOLS, ...extra].sort(), + ); + }, + ); + + // The async task tools went with SDK v2's experimental tasks layer + // (SEP-2663). Part 5 (#4852) brings them back on the tasks extension. + it.skip.each([ [ { sampling: {}, @@ -166,7 +170,7 @@ describe("tools/list", () => { [{ tasks: { requests: { sampling: { createMessage: {} } } } }, []], [{ tasks: { requests: { elicitation: { create: {} } } } }, []], ])( - "gates tools on the client's declared capabilities: %j", + "gates the async task tools on the client's declared capabilities: %j", async (capabilities, extra) => { session = await connect({ capabilities }); const { tools } = await session.client.listTools(); @@ -177,7 +181,9 @@ describe("tools/list", () => { ); it("sends tools/list_changed after initialize, when the conditional tools are registered", async () => { - session = await connect(); + // A client with no capabilities gets no conditional tools, so nothing is + // registered after initialize and no list_changed is sent. + session = await connect({ capabilities: ALL_CAPABILITIES }); await session.client.listTools(); expect( ofMethod(session.notifications, "notifications/tools/list_changed") @@ -210,9 +216,7 @@ describe("tools/list", () => { "openWorldHint": false, "readOnlyHint": true, }, - "execution": { - "taskSupport": "forbidden", - }, + "execution": undefined, "hasOutputSchema": false, "title": "Echo Tool", }, @@ -223,9 +227,7 @@ describe("tools/list", () => { "openWorldHint": false, "readOnlyHint": true, }, - "execution": { - "taskSupport": "forbidden", - }, + "execution": undefined, "hasOutputSchema": false, "title": "Get Annotated Message Tool", }, @@ -236,9 +238,7 @@ describe("tools/list", () => { "openWorldHint": false, "readOnlyHint": true, }, - "execution": { - "taskSupport": "forbidden", - }, + "execution": undefined, "hasOutputSchema": false, "title": "Print Environment Tool", }, @@ -249,9 +249,7 @@ describe("tools/list", () => { "openWorldHint": false, "readOnlyHint": true, }, - "execution": { - "taskSupport": "forbidden", - }, + "execution": undefined, "hasOutputSchema": false, "title": "Get Resource Links Tool", }, @@ -262,9 +260,7 @@ describe("tools/list", () => { "openWorldHint": false, "readOnlyHint": true, }, - "execution": { - "taskSupport": "forbidden", - }, + "execution": undefined, "hasOutputSchema": false, "title": "Get Resource Reference Tool", }, @@ -275,9 +271,7 @@ describe("tools/list", () => { "openWorldHint": false, "readOnlyHint": true, }, - "execution": { - "taskSupport": "forbidden", - }, + "execution": undefined, "hasOutputSchema": false, "title": "Get Roots List Tool", }, @@ -288,9 +282,7 @@ describe("tools/list", () => { "openWorldHint": false, "readOnlyHint": true, }, - "execution": { - "taskSupport": "forbidden", - }, + "execution": undefined, "hasOutputSchema": true, "title": "Get Structured Content Tool", }, @@ -301,9 +293,7 @@ describe("tools/list", () => { "openWorldHint": false, "readOnlyHint": true, }, - "execution": { - "taskSupport": "forbidden", - }, + "execution": undefined, "hasOutputSchema": false, "title": "Get Sum Tool", }, @@ -314,9 +304,7 @@ describe("tools/list", () => { "openWorldHint": false, "readOnlyHint": true, }, - "execution": { - "taskSupport": "forbidden", - }, + "execution": undefined, "hasOutputSchema": false, "title": "Get Tiny Image Tool", }, @@ -327,25 +315,10 @@ describe("tools/list", () => { "openWorldHint": true, "readOnlyHint": false, }, - "execution": { - "taskSupport": "forbidden", - }, + "execution": undefined, "hasOutputSchema": false, "title": "GZip File as Resource Tool", }, - "simulate-research-query": { - "annotations": { - "destructiveHint": false, - "idempotentHint": false, - "openWorldHint": false, - "readOnlyHint": false, - }, - "execution": { - "taskSupport": "required", - }, - "hasOutputSchema": false, - "title": "Simulate Research Query", - }, "toggle-simulated-logging": { "annotations": { "destructiveHint": false, @@ -353,9 +326,7 @@ describe("tools/list", () => { "openWorldHint": false, "readOnlyHint": false, }, - "execution": { - "taskSupport": "forbidden", - }, + "execution": undefined, "hasOutputSchema": false, "title": "Toggle Simulated Logging", }, @@ -366,9 +337,7 @@ describe("tools/list", () => { "openWorldHint": false, "readOnlyHint": false, }, - "execution": { - "taskSupport": "forbidden", - }, + "execution": undefined, "hasOutputSchema": false, "title": "Toggle Subscriber Updates", }, @@ -379,25 +348,10 @@ describe("tools/list", () => { "openWorldHint": false, "readOnlyHint": false, }, - "execution": { - "taskSupport": "forbidden", - }, + "execution": undefined, "hasOutputSchema": false, "title": "Trigger Elicitation Request Tool", }, - "trigger-elicitation-request-async": { - "annotations": { - "destructiveHint": false, - "idempotentHint": false, - "openWorldHint": false, - "readOnlyHint": false, - }, - "execution": { - "taskSupport": "forbidden", - }, - "hasOutputSchema": false, - "title": "Trigger Async Elicitation Request Tool", - }, "trigger-long-running-operation": { "annotations": { "destructiveHint": false, @@ -405,9 +359,7 @@ describe("tools/list", () => { "openWorldHint": false, "readOnlyHint": true, }, - "execution": { - "taskSupport": "forbidden", - }, + "execution": undefined, "hasOutputSchema": false, "title": "Trigger Long Running Operation Tool", }, @@ -418,25 +370,10 @@ describe("tools/list", () => { "openWorldHint": true, "readOnlyHint": false, }, - "execution": { - "taskSupport": "forbidden", - }, + "execution": undefined, "hasOutputSchema": false, "title": "Trigger Sampling Request Tool", }, - "trigger-sampling-request-async": { - "annotations": { - "destructiveHint": false, - "idempotentHint": false, - "openWorldHint": true, - "readOnlyHint": false, - }, - "execution": { - "taskSupport": "forbidden", - }, - "hasOutputSchema": false, - "title": "Trigger Async Sampling Request Tool", - }, "trigger-url-elicitation": { "annotations": { "destructiveHint": false, @@ -444,9 +381,7 @@ describe("tools/list", () => { "openWorldHint": true, "readOnlyHint": false, }, - "execution": { - "taskSupport": "forbidden", - }, + "execution": undefined, "hasOutputSchema": false, "title": "Trigger URL Elicitation Tool", }, @@ -465,7 +400,7 @@ describe("tools/list", () => { expect(schemas).toMatchInlineSnapshot(` { "echo": { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "message": { "description": "Message to echo", @@ -478,7 +413,7 @@ describe("tools/list", () => { "type": "object", }, "get-annotated-message": { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "includeImage": { "default": false, @@ -501,12 +436,12 @@ describe("tools/list", () => { "type": "object", }, "get-env": { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": {}, "type": "object", }, "get-resource-links": { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "count": { "default": 3, @@ -519,7 +454,7 @@ describe("tools/list", () => { "type": "object", }, "get-resource-reference": { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "resourceId": { "default": 1, @@ -538,12 +473,12 @@ describe("tools/list", () => { "type": "object", }, "get-roots-list": { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": {}, "type": "object", }, "get-structured-content": { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "location": { "description": "Choose city", @@ -561,7 +496,7 @@ describe("tools/list", () => { "type": "object", }, "get-sum": { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "a": { "description": "First number", @@ -579,12 +514,12 @@ describe("tools/list", () => { "type": "object", }, "get-tiny-image": { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": {}, "type": "object", }, "gzip-file-as-resource": { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "data": { "default": "https://raw.githubusercontent.com/modelcontextprotocol/servers/refs/heads/main/README.md", @@ -609,46 +544,23 @@ describe("tools/list", () => { }, "type": "object", }, - "simulate-research-query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "ambiguous": { - "default": false, - "description": "Simulate an ambiguous query that requires clarification (triggers input_required status)", - "type": "boolean", - }, - "topic": { - "description": "The research topic to investigate", - "type": "string", - }, - }, - "required": [ - "topic", - ], - "type": "object", - }, "toggle-simulated-logging": { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": {}, "type": "object", }, "toggle-subscriber-updates": { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": {}, "type": "object", }, "trigger-elicitation-request": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": {}, - "type": "object", - }, - "trigger-elicitation-request-async": { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": {}, "type": "object", }, "trigger-long-running-operation": { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "duration": { "default": 10, @@ -664,25 +576,7 @@ describe("tools/list", () => { "type": "object", }, "trigger-sampling-request": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "maxTokens": { - "default": 100, - "description": "Maximum number of tokens to generate", - "type": "number", - }, - "prompt": { - "description": "The prompt to send to the LLM", - "type": "string", - }, - }, - "required": [ - "prompt", - ], - "type": "object", - }, - "trigger-sampling-request-async": { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "maxTokens": { "default": 100, @@ -700,7 +594,7 @@ describe("tools/list", () => { "type": "object", }, "trigger-url-elicitation": { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "elicitationId": { "description": "Optional explicit elicitation ID. Defaults to a random UUID.", @@ -737,7 +631,7 @@ describe("tools/list", () => { const tool = tools.find((t) => t.name === "get-structured-content"); expect(tool?.outputSchema).toMatchInlineSnapshot(` { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "conditions": { @@ -771,12 +665,15 @@ describe("prompts/list", () => { expect(prompts).toMatchInlineSnapshot(` [ { + "_meta": undefined, "arguments": undefined, "description": "A prompt with no arguments", + "icons": undefined, "name": "simple-prompt", "title": "Simple Prompt", }, { + "_meta": undefined, "arguments": [ { "description": "Name of the city", @@ -784,16 +681,18 @@ describe("prompts/list", () => { "required": true, }, { - "description": undefined, + "description": "Name of the state", "name": "state", "required": false, }, ], "description": "A prompt with two arguments, one required and one optional", + "icons": undefined, "name": "args-prompt", "title": "Arguments Prompt", }, { + "_meta": undefined, "arguments": [ { "description": "Choose the department.", @@ -807,10 +706,12 @@ describe("prompts/list", () => { }, ], "description": "First argument choice narrows values for second argument.", + "icons": undefined, "name": "completable-prompt", "title": "Team Management", }, { + "_meta": undefined, "arguments": [ { "description": "Type of resource to fetch", @@ -824,6 +725,7 @@ describe("prompts/list", () => { }, ], "description": "A prompt that includes an embedded resource reference", + "icons": undefined, "name": "resource-prompt", "title": "Resource Prompt", }, diff --git a/src/everything/__tests__/tasks.test.ts b/src/everything/__tests__/tasks.test.ts index 6a6fa61e88..1d8318b395 100644 --- a/src/everything/__tests__/tasks.test.ts +++ b/src/everything/__tests__/tasks.test.ts @@ -11,19 +11,100 @@ * capabilities and keeps its tasks in an `InMemoryTaskStore`. * * Every wait in these tools is a `setTimeout` (one-second stages and polls), - * so the tests fake `setTimeout` and advance the clock. + * so the tests fake `setTimeout` and advance the clock. * + * SDK v2 removed the experimental tasks layer these tools and tests were + * built on (SEP-2663), so the three tools are gone and every test here is + * skipped until Part 5 (#4852) re-implements them on the + * `io.modelcontextprotocol/tasks` extension. The tests are kept, not deleted, + * as the record of the behavior Part 5 restores. The v1 client surface they + * drive (`client.experimental.tasks`, `InMemoryTaskStore`, task-returning + * request handlers) is typed by the stand-ins below so the bodies still + * compile; calling any of them throws. */ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { InMemoryTaskStore } from "@modelcontextprotocol/sdk/experimental/tasks"; -import { - CallToolResultSchema, - CreateMessageRequestSchema, - ElicitRequestSchema, - type ElicitRequest, - type ElicitResult, -} from "@modelcontextprotocol/sdk/types.js"; +import type { + CallToolResult, + Client, + ElicitRequest, + ElicitResult, + GetTaskResult, +} from "@modelcontextprotocol/client"; +import { CallToolResultSchema } from "@modelcontextprotocol/core"; import { connect, contentOf, textOf, type Session } from "./harness.js"; +/** A task, as the v1 tasks API reported it. */ +type Task = GetTaskResult; + +/** One message of v1's `callToolStream`. */ +type TaskStreamMessage = + | { type: "taskCreated"; task: Task } + | { type: "taskStatus"; task: Task } + | { type: "result"; result: CallToolResult } + | { type: "error"; error: Error }; + +const REMOVED = + "SDK v2 removed the experimental tasks API (SEP-2663); see #4852"; + +/** Stand-in for v1's `client.experimental.tasks`. */ +function experimentalTasks(_client: Client): { + callToolStream: ( + params: { name: string; arguments: Record }, + resultSchema: unknown, + ) => AsyncGenerator; + listTasks: () => Promise<{ tasks: Task[] }>; + cancelTask: (taskId: string) => Promise; + getTask: (taskId: string) => Promise; +} { + throw new Error(REMOVED); +} + +/** Stand-in for v1's client-side `InMemoryTaskStore`. */ +class InMemoryTaskStore { + constructor() { + throw new Error(REMOVED); + } + createTask( + _params: { ttl?: number }, + _requestId: number, + _request: unknown, + ): Promise { + throw new Error(REMOVED); + } + getTask(_taskId: string): Promise { + throw new Error(REMOVED); + } + storeTaskResult( + _taskId: string, + _status: "completed" | "failed", + _result: Record, + ): Promise { + throw new Error(REMOVED); + } + updateTaskStatus( + _taskId: string, + _status: Task["status"], + _statusMessage?: string, + ): Promise { + throw new Error(REMOVED); + } +} + +/** + * Stand-in for a v1 client request handler that answers a task-augmented + * request with a `CreateTaskResult`, which v2's typed handlers do not allow. + */ +function setTaskRequestHandler< + M extends "sampling/createMessage" | "elicitation/create", +>( + _client: Client, + _method: M, + _handler: (request: { + params: { task?: { ttl?: number } }; + }) => Promise<{ task: Task }>, +): void { + throw new Error(REMOVED); +} + let session: Session | undefined; beforeEach(() => { @@ -65,7 +146,7 @@ describe("simulate-research-query", () => { await s.client.listTools(); // lets callToolStream see taskSupport return settle( collect( - s.client.experimental.tasks.callToolStream( + experimentalTasks(s.client).callToolStream( { name: "simulate-research-query", arguments: args }, CallToolResultSchema, ), @@ -85,7 +166,7 @@ describe("simulate-research-query", () => { return textOf(contentOf(last.result)[0]); } - it("creates a task that works through four stages and completes with a report", async () => { + it.skip("creates a task that works through four stages and completes with a report", async () => { session = await connect(); const messages = await research({ topic: "tides" }); @@ -140,7 +221,7 @@ describe("simulate-research-query", () => { `); }); - it("is rejected as a tool error when called without a task", async () => { + it.skip("is rejected as a tool error when called without a task", async () => { session = await connect(); const result = await session.client.callTool({ name: "simulate-research-query", @@ -157,7 +238,7 @@ describe("simulate-research-query", () => { }); }); - it("ignores 'ambiguous' for a client without elicitation", async () => { + it.skip("ignores 'ambiguous' for a client without elicitation", async () => { session = await connect(); const messages = await research({ topic: "python", ambiguous: true }); expect(statusMessages(messages)).not.toContain( @@ -174,7 +255,7 @@ describe("simulate-research-query", () => { session = await connect({ capabilities: { elicitation: { form: {} } }, setup: (client) => - client.setRequestHandler(ElicitRequestSchema, async (req) => { + client.setRequestHandler("elicitation/create", async (req) => { asked.push(req.params); return answer(req); }), @@ -182,7 +263,7 @@ describe("simulate-research-query", () => { return asked; } - it("asks for a clarification through tasks/result, then completes with it", async () => { + it.skip("asks for a clarification through tasks/result, then completes with it", async () => { const asked = await connectAnswering(() => ({ action: "accept", content: { interpretation: "programming" }, @@ -221,7 +302,7 @@ describe("simulate-research-query", () => { ); }); - it("offers generic interpretations for other topics", async () => { + it.skip("offers generic interpretations for other topics", async () => { const asked = await connectAnswering(() => ({ action: "decline" })); await research({ topic: "Mercury", ambiguous: true }); const params = asked[0]; @@ -236,7 +317,7 @@ describe("simulate-research-query", () => { ); }); - it.each([ + it.skip.each([ [{ action: "decline" }, "User declined - using default interpretation"], [{ action: "cancel" }, "User cancelled - using default interpretation"], [{ action: "accept", content: {} }, "User accepted without selection"], @@ -253,7 +334,7 @@ describe("simulate-research-query", () => { }, ); - it("falls back to a default interpretation when the elicitation fails", async () => { + it.skip("falls back to a default interpretation when the elicitation fails", async () => { const warn = vi.spyOn(console, "warn").mockImplementation(() => {}); await connectAnswering(() => { throw new Error("no UI"); @@ -273,12 +354,12 @@ describe("simulate-research-query", () => { }); }); - it("lists its task, and stops working on it once the client cancels it", async () => { + it.skip("lists its task, and stops working on it once the client cancels it", async () => { const errors = vi.spyOn(console, "error").mockImplementation(() => {}); session = await connect(); const { client } = session; await client.listTools(); - const stream = client.experimental.tasks.callToolStream( + const stream = experimentalTasks(client).callToolStream( { name: "simulate-research-query", arguments: { topic: "tides" } }, CallToolResultSchema, ); @@ -289,10 +370,10 @@ describe("simulate-research-query", () => { const { taskId } = first.value.task; await stream.return(undefined); - const { tasks } = await client.experimental.tasks.listTasks(); + const { tasks } = await experimentalTasks(client).listTasks(); expect(tasks.map((t) => t.taskId)).toContain(taskId); - await client.experimental.tasks.cancelTask(taskId); + await experimentalTasks(client).cancelTask(taskId); // The next stage's status update fails on the cancelled task; the // failure is logged, marking it failed fails too, and it stays cancelled. await vi.advanceTimersByTimeAsync(5000); @@ -300,7 +381,7 @@ describe("simulate-research-query", () => { `Research task ${taskId} failed:`, expect.any(Error), ); - expect(await client.experimental.tasks.getTask(taskId)).toMatchObject({ + expect(await experimentalTasks(client).getTask(taskId)).toMatchObject({ status: "cancelled", statusMessage: "Client cancelled task execution.", }); @@ -346,12 +427,11 @@ async function connectTaskClient( }, }, }, - taskStore, setup: (client) => { - client.setRequestHandler(CreateMessageRequestSchema, async (req) => + setTaskRequestHandler(client, "sampling/createMessage", async (req) => createTask("sampling", req, req.params.task?.ttl), ); - client.setRequestHandler(ElicitRequestSchema, async (req) => + setTaskRequestHandler(client, "elicitation/create", async (req) => createTask("elicitation", req, req.params.task?.ttl), ); }, @@ -361,9 +441,12 @@ async function connectTaskClient( async function callAsync(name: string, args: Record = {}) { const result = await settle( - session!.client.callTool({ name, arguments: args }, undefined, { - timeout: 3_600_000, - }), + session!.client.callTool( + { name, arguments: args }, + { + timeout: 3_600_000, + }, + ), 700_000, ); return contentOf(result).map(textOf); @@ -381,7 +464,7 @@ const SAMPLE = { }; describe("trigger-sampling-request-async", () => { - it("sends a task-augmented sampling request, polls the client's task, and returns its result", async () => { + it.skip("sends a task-augmented sampling request, polls the client's task, and returns its result", async () => { let created = ""; const { taskStore } = await connectTaskClient((_method, taskId) => { created = taskId!; @@ -408,7 +491,7 @@ describe("trigger-sampling-request-async", () => { ); }); - it("reports a failed client task with its status message", async () => { + it.skip("reports a failed client task with its status message", async () => { const { taskStore } = await connectTaskClient((_m, taskId) => { later(() => taskStore.updateTaskStatus(taskId!, "failed", "model overloaded"), @@ -422,7 +505,7 @@ describe("trigger-sampling-request-async", () => { ); }); - it("reports the status message of a client task that is already finished when it is created", async () => { + it.skip("reports the status message of a client task that is already finished when it is created", async () => { // A task that fails before it is returned is never polled, so its status // message comes from the CreateTaskResult. const { taskStore } = await connectTaskClient((_m, taskId) => { @@ -436,7 +519,7 @@ describe("trigger-sampling-request-async", () => { ); }); - it("reports a cancelled client task with no message", async () => { + it.skip("reports a cancelled client task with no message", async () => { const { taskStore } = await connectTaskClient((_m, taskId) => { later(() => taskStore.updateTaskStatus(taskId!, "cancelled")); }); @@ -446,7 +529,7 @@ describe("trigger-sampling-request-async", () => { expect(text).toMatch(/^\[CANCELLED\] No message\n\nProgress:\n/); }); - it("gives up after 60 polls of a task that never finishes", async () => { + it.skip("gives up after 60 polls of a task that never finishes", async () => { await connectTaskClient(() => {}); const [text] = await callAsync("trigger-sampling-request-async", { prompt: "p", @@ -459,7 +542,7 @@ describe("trigger-sampling-request-async", () => { ); }); - it("returns a synchronous answer from a client that ignores the task request", async () => { + it.skip("returns a synchronous answer from a client that ignores the task request", async () => { session = await connect({ capabilities: { sampling: {}, @@ -481,7 +564,7 @@ describe("trigger-sampling-request-async", () => { }); describe("trigger-elicitation-request-async", () => { - it("sends a task-augmented elicitation and summarizes the accepted answer", async () => { + it.skip("sends a task-augmented elicitation and summarizes the accepted answer", async () => { const asked: string[] = []; const { taskStore } = await connectTaskClient((method, taskId) => { asked.push(method); @@ -505,7 +588,7 @@ describe("trigger-elicitation-request-async", () => { ); }); - it("summarizes an accepted answer with no fields", async () => { + it.skip("summarizes an accepted answer with no fields", async () => { const { taskStore } = await connectTaskClient((_m, taskId) => { later(() => taskStore.storeTaskResult(taskId!, "completed", { @@ -521,7 +604,7 @@ describe("trigger-elicitation-request-async", () => { ]); }); - it.each([ + it.skip.each([ [ "decline", "[DECLINED] User declined to provide the requested information.", @@ -535,7 +618,7 @@ describe("trigger-elicitation-request-async", () => { expect(texts[0]).toBe(expected); }); - it("reports only progress and the raw result for an answer of another shape", async () => { + it.skip("reports only progress and the raw result for an answer of another shape", async () => { const { taskStore } = await connectTaskClient((_m, taskId) => { later(() => taskStore.storeTaskResult(taskId!, "completed", { action: "accept" }), @@ -546,7 +629,7 @@ describe("trigger-elicitation-request-async", () => { expect(texts[0]).toMatch(/^\nProgress:\n/); }); - it("logs the first poll, every tenth, and every status change while input is required", async () => { + it.skip("logs the first poll, every tenth, and every status change while input is required", async () => { let taskId = ""; const { taskStore } = await connectTaskClient((_m, id) => { taskId = id!; @@ -568,7 +651,7 @@ describe("trigger-elicitation-request-async", () => { expect(polls).toHaveLength(4); }); - it("reports the status message of a client task that is already finished when it is created", async () => { + it.skip("reports the status message of a client task that is already finished when it is created", async () => { // A task that fails before it is returned is never polled, so its status // message comes from the CreateTaskResult. const { taskStore } = await connectTaskClient((_m, taskId) => { @@ -582,7 +665,7 @@ describe("trigger-elicitation-request-async", () => { ]); }); - it("reports a failed client task", async () => { + it.skip("reports a failed client task", async () => { const { taskStore } = await connectTaskClient((_m, taskId) => { later(() => taskStore.updateTaskStatus(taskId!, "failed")); }); @@ -597,7 +680,7 @@ describe("trigger-elicitation-request-async", () => { }); } - it("gives up 5 seconds before the 10-minute TTL of a task that never finishes", async () => { + it.skip("gives up 5 seconds before the 10-minute TTL of a task that never finishes", async () => { fakeClock(); // A client that keeps its task longer than the 10-minute TTL asked for. await connectTaskClient(() => {}, 60_000); @@ -607,7 +690,7 @@ describe("trigger-elicitation-request-async", () => { ); }); - it("times out before a client that honors the 10-minute TTL expires the task", async () => { + it.skip("times out before a client that honors the 10-minute TTL expires the task", async () => { fakeClock(); let created = ""; const { taskStore } = await connectTaskClient((_m, taskId) => { @@ -625,7 +708,7 @@ describe("trigger-elicitation-request-async", () => { expect((await taskStore.getTask(created))?.ttl).toBe(600000); }); - it("times out without polling when its wait ends past the deadline", async () => { + it.skip("times out without polling when its wait ends past the deadline", async () => { // A timer that fires late: the first reading sets the deadline, and every // later one is already past it. let readings = 0; @@ -641,7 +724,7 @@ describe("trigger-elicitation-request-async", () => { ]); }); - it("returns a synchronous answer from a client that ignores the task request", async () => { + it.skip("returns a synchronous answer from a client that ignores the task request", async () => { session = await connect({ capabilities: { elicitation: {}, diff --git a/src/everything/__tests__/tools.test.ts b/src/everything/__tests__/tools.test.ts index 56650c47b6..66aef69288 100644 --- a/src/everything/__tests__/tools.test.ts +++ b/src/everything/__tests__/tools.test.ts @@ -44,7 +44,7 @@ describe("echo", () => { const result = await call("echo", {}); expect(result.isError).toBe(true); expect(textOf(contentOf(result)[0])).toMatch( - /^MCP error -32602: Input validation error: Invalid arguments for tool echo/, + /^Input validation error: Invalid arguments for tool echo/, ); }); }); @@ -175,7 +175,6 @@ describe("trigger-long-running-operation", () => { name: "trigger-long-running-operation", arguments: { duration: 0.03, steps: 3 }, }, - undefined, { onprogress: (p) => progress.push(p) }, ); expect(textOf(contentOf(result)[0])).toBe( @@ -308,11 +307,10 @@ describe("get-resource-reference", () => { }); describe("an unknown tool", () => { - it("is reported as a tool error", async () => { - const result = await call("no-such-tool"); - expect(result.isError).toBe(true); - expect(textOf(contentOf(result)[0])).toBe( - "MCP error -32602: Tool no-such-tool not found", - ); + it("is rejected with -32602", async () => { + await expect(call("no-such-tool")).rejects.toMatchObject({ + code: -32602, + message: "Tool no-such-tool not found", + }); }); }); diff --git a/src/everything/docs/features.md b/src/everything/docs/features.md index 84327a6987..12f8435130 100644 --- a/src/everything/docs/features.md +++ b/src/everything/docs/features.md @@ -25,9 +25,6 @@ - `trigger-elicitation-request` (tools/trigger-elicitation-request.ts): Issues an `elicitation/create` request using form-mode fields (strings, numbers, booleans, enums, and format validation) and returns the resulting action/content. Requires client form-mode elicitation: `elicitation.form`, or an `elicitation` object that declares neither mode. - `trigger-url-elicitation` (tools/trigger-url-elicitation.ts): Issues an `elicitation/create` request in URL mode (`mode: "url"`) with an `elicitationId`, or throws MCP error `-32042` (`UrlElicitationRequiredError`) when `errorPath=true`. On the error path the prerequisite elicitation it returns points at a different URL than the failing request (`https://modelcontextprotocol.io`); when the client satisfies it and retries the same call, the retry ignores `errorPath` and proceeds via the request path, so the client does not loop on the same error. The retry marker is one-shot per `(session, url, elicitationId)`: it is cleared on the recognized retry, so re-running the error path with identical arguments without an intervening prerequisite is treated as a retry and proceeds. Requires client capability `elicitation.url`. - `trigger-sampling-request` (tools/trigger-sampling-request.ts): Issues a `sampling/createMessage` request to the client/LLM using provided `prompt` and optional generation controls; returns the LLM's response payload. -- `simulate-research-query` (tools/simulate-research-query.ts): Demonstrates MCP Tasks (SEP-1686) with a simulated multi-stage research operation. Accepts `topic` and `ambiguous` parameters. Returns a task that progresses through stages with status updates. If `ambiguous` is true and client supports elicitation, sends an elicitation request directly to gather clarification before completing. -- `trigger-sampling-request-async` (tools/trigger-sampling-request-async.ts): Demonstrates bidirectional tasks where the server sends a sampling request that the client executes as a background task. Server polls for status and retrieves the LLM result when complete. Requires client to support `tasks.requests.sampling.createMessage`. -- `trigger-elicitation-request-async` (tools/trigger-elicitation-request-async.ts): Demonstrates bidirectional tasks where the server sends an elicitation request that the client executes as a background task. Server polls while waiting for user input. Requires client form-mode elicitation (as for `trigger-elicitation-request`) and `tasks.requests.elicitation.create`. ## Prompts @@ -56,50 +53,11 @@ - Use the `toggle-simulated-logging` tool to start/stop periodic log messages of varying levels (debug, info, notice, warning, error, critical, alert, emergency) per session. - Clients can control the minimum level they receive via the standard MCP `logging/setLevel` request. -## Tasks (SEP-1686) +## Tasks -The server advertises support for MCP Tasks, enabling long-running operations with status tracking: - -- **Capabilities advertised**: `tasks.list`, `tasks.cancel`, `tasks.requests.tools.call` -- **Task Store**: Uses `InMemoryTaskStore` from SDK experimental for task lifecycle management -- **Message Queue**: Uses `InMemoryTaskMessageQueue` for task-related messaging - -### Task Lifecycle - -1. Client calls `tools/call` with `task: true` parameter -2. Server returns `CreateTaskResult` with `taskId` instead of immediate result -3. Client polls `tasks/get` to check status and receive `statusMessage` updates -4. When status is `completed`, client calls `tasks/result` to retrieve the final result - -### Task Statuses - -- `working`: Task is actively processing -- `input_required`: Task needs additional input (server sends elicitation request directly) -- `completed`: Task finished successfully -- `failed`: Task encountered an error -- `cancelled`: Task was cancelled by client - -### Demo Tools - -**Server-side tasks (client calls server):** -Use the `simulate-research-query` tool to exercise the full task lifecycle. Set `ambiguous: true` to trigger elicitation - the server will send an `elicitation/create` request directly and await the response before completing. - -**Client-side tasks (server calls client):** -Use `trigger-sampling-request-async` or `trigger-elicitation-request-async` to demonstrate bidirectional tasks where the server sends requests that the client executes as background tasks. These require the client to advertise `sampling` with `tasks.requests.sampling.createMessage`, or form-mode `elicitation` with `tasks.requests.elicitation.create`, respectively. - -### Bidirectional Task Flow - -MCP Tasks are bidirectional - both server and client can be task executors: - -| Direction | Request Type | Task Executor | Demo Tool | -| ---------------- | ------------------------ | ------------- | ----------------------------------- | -| Client -> Server | `tools/call` | Server | `simulate-research-query` | -| Server -> Client | `sampling/createMessage` | Client | `trigger-sampling-request-async` | -| Server -> Client | `elicitation/create` | Client | `trigger-elicitation-request-async` | - -For client-side tasks: - -1. Server sends request with task metadata (e.g., `params.task.ttl`) -2. Client creates task and returns `CreateTaskResult` with `taskId` -3. Server polls `tasks/get` for status updates -4. When complete, server calls `tasks/result` to retrieve the result +The server does not support MCP Tasks at the moment. Its three task tools +(`simulate-research-query`, `trigger-sampling-request-async` and +`trigger-elicitation-request-async`) were built on the TypeScript SDK's +experimental tasks layer, which SDK v2 removed (SEP-2663). They return on the +`io.modelcontextprotocol/tasks` extension in +[#4852](https://github.com/modelcontextprotocol/servers/issues/4852). diff --git a/src/everything/docs/instructions.md b/src/everything/docs/instructions.md index 4e6a1e0ac4..2abe5f916a 100644 --- a/src/everything/docs/instructions.md +++ b/src/everything/docs/instructions.md @@ -23,8 +23,6 @@ Registered only if your client declared the capability shown. Otherwise they are - `trigger-sampling-request`: `sampling` - `trigger-elicitation-request`: `elicitation.form`, or `elicitation` with no mode - `trigger-url-elicitation`: `elicitation.url` -- `trigger-sampling-request-async`: `sampling` and `tasks.requests.sampling.createMessage` -- `trigger-elicitation-request-async`: form-mode `elicitation` and `tasks.requests.elicitation.create` ## Operational Patterns diff --git a/src/everything/docs/structure.md b/src/everything/docs/structure.md index 24530e03af..9aac31847d 100644 --- a/src/everything/docs/structure.md +++ b/src/everything/docs/structure.md @@ -48,14 +48,11 @@ src/everything │ ├── get-sum.ts │ ├── get-tiny-image.ts │ ├── gzip-file-as-resource.ts - │ ├── simulate-research-query.ts │ ├── toggle-simulated-logging.ts │ ├── toggle-subscriber-updates.ts │ ├── trigger-elicitation-request.ts - │ ├── trigger-elicitation-request-async.ts │ ├── trigger-long-running-operation.ts │ ├── trigger-sampling-request.ts - │ ├── trigger-sampling-request-async.ts │ └── trigger-url-elicitation.ts └── transports ├── in-memory-event-store.ts @@ -82,7 +79,7 @@ src/everything - Package metadata and scripts: - `build`: TypeScript compile to `dist/`, copies `docs/` into `dist/` and marks the compiled entry scripts as executable. - `start:stdio`, `start:sse`, `start:streamableHttp`: Run built transports from `dist/`. -- Declares dependencies on `@modelcontextprotocol/sdk`, `express`, `cors`, `zod`, etc. +- Declares dependencies on the TypeScript SDK v2 packages (`@modelcontextprotocol/server`, `@modelcontextprotocol/node`, `@modelcontextprotocol/server-legacy`), `express`, `cors`, `zod`, etc. ### `docs/` @@ -163,18 +160,12 @@ src/everything - `GZIP_MAX_FETCH_SIZE` (bytes, default 10 MiB) - `GZIP_MAX_FETCH_TIME_MILLIS` (ms, default 30000) - `GZIP_ALLOWED_DOMAINS` (comma-separated allowlist; empty means all domains allowed) -- `simulate-research-query.ts` - - Registers a `simulate-research-query` task-based tool that demonstrates the MCP Tasks feature (SEP-1686). Simulates a multi-stage research operation with progress updates. If the query is marked as ambiguous and the client supports elicitation, it pauses mid-execution to request clarification via `elicitation/create`. Uses `server.experimental.tasks.registerToolTask()` with `execution: { taskSupport: "required" }`. - `trigger-elicitation-request.ts` - Registers a `trigger-elicitation-request` tool that sends an `elicitation/create` request to the client/LLM and returns the elicitation result. - `trigger-url-elicitation.ts` - Registers a `trigger-url-elicitation` tool that either sends an out-of-band URL-mode `elicitation/create` request (`mode: "url"`) including an `elicitationId` (request path) or throws `UrlElicitationRequiredError` (`-32042`) for client-handled URL elicitation (error path). On the error path the carried prerequisite elicitation points at a different URL than the failing one (`https://modelcontextprotocol.io`), and when the client satisfies it and retries the same call, the retry ignores `errorPath` and proceeds via the request path — so the client does not loop on the same error. -- `trigger-elicitation-request-async.ts` - - Registers a `trigger-elicitation-request-async` tool that demonstrates bidirectional MCP tasks for elicitation. Sends an elicitation request with task metadata, then polls the client's `tasks/get` endpoint for completion status before fetching the final result. - `trigger-sampling-request.ts` - Registers a `trigger-sampling-request` tool that sends a `sampling/createMessage` request to the client/LLM and returns the sampling result. -- `trigger-sampling-request-async.ts` - - Registers a `trigger-sampling-request-async` tool that demonstrates bidirectional MCP tasks for sampling. Sends a sampling request with task metadata, then polls the client's `tasks/get` endpoint for completion status before fetching the final result. - `get-structured-content.ts` - Registers a `get-structured-content` tool that demonstrates structuredContent block responses. - `get-sum.ts` diff --git a/src/everything/package.json b/src/everything/package.json index e65007cdc4..51ce62ead7 100644 --- a/src/everything/package.json +++ b/src/everything/package.json @@ -12,6 +12,9 @@ "url": "https://github.com/modelcontextprotocol/servers.git" }, "type": "module", + "engines": { + "node": ">=20" + }, "bin": { "mcp-server-everything": "dist/index.js" }, @@ -34,13 +37,17 @@ "validate": "npm run format:check && npm run lint && npm run typecheck && npm run build && npm run test" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.30.0", + "@modelcontextprotocol/node": "^2.1.1", + "@modelcontextprotocol/server": "^2.3.1", + "@modelcontextprotocol/server-legacy": "^2.3.1", "cors": "^2.8.5", "express": "^5.2.1", "jszip": "^3.10.1", - "zod": "^4.0.0" + "zod": "^4.2.0" }, "devDependencies": { + "@modelcontextprotocol/client": "^2.3.1", + "@modelcontextprotocol/core": "^2.3.1", "@types/cors": "^2.8.19", "@types/express": "^5.0.6", "@vitest/coverage-v8": "^4.1.8", diff --git a/src/everything/prompts/args.ts b/src/everything/prompts/args.ts index bbd3741799..d3113d3687 100644 --- a/src/everything/prompts/args.ts +++ b/src/everything/prompts/args.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from "@modelcontextprotocol/server"; /** * Register a prompt with arguments @@ -10,10 +10,10 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; */ export const registerArgumentsPrompt = (server: McpServer) => { // Prompt arguments - const promptArgsSchema = { + const promptArgsSchema = z.object({ city: z.string().describe("Name of the city"), state: z.string().describe("Name of the state").optional(), - }; + }); // Register the prompt server.registerPrompt( diff --git a/src/everything/prompts/completions.ts b/src/everything/prompts/completions.ts index f7c76d6b8e..aff3c77f77 100644 --- a/src/everything/prompts/completions.ts +++ b/src/everything/prompts/completions.ts @@ -1,6 +1,5 @@ import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { completable } from "@modelcontextprotocol/sdk/server/completable.js"; +import { McpServer, completable } from "@modelcontextprotocol/server"; /** * Register a prompt with completable arguments @@ -12,7 +11,7 @@ import { completable } from "@modelcontextprotocol/sdk/server/completable.js"; */ export const registerPromptWithCompletions = (server: McpServer) => { // Prompt arguments - const promptArgsSchema = { + const promptArgsSchema = z.object({ department: completable( z.string().describe("Choose the department."), (value) => { @@ -39,7 +38,7 @@ export const registerPromptWithCompletions = (server: McpServer) => { return []; }, ), - }; + }); // Register the prompt server.registerPrompt( diff --git a/src/everything/prompts/index.ts b/src/everything/prompts/index.ts index 6efa7b7297..22d6a22baf 100644 --- a/src/everything/prompts/index.ts +++ b/src/everything/prompts/index.ts @@ -1,4 +1,4 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from "@modelcontextprotocol/server"; import { registerSimplePrompt } from "./simple.js"; import { registerArgumentsPrompt } from "./args.js"; import { registerPromptWithCompletions } from "./completions.js"; diff --git a/src/everything/prompts/resource.ts b/src/everything/prompts/resource.ts index e825941673..5b96aba194 100644 --- a/src/everything/prompts/resource.ts +++ b/src/everything/prompts/resource.ts @@ -1,4 +1,5 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from "@modelcontextprotocol/server"; +import { z } from "zod"; import { resourceTypeCompleter, resourceIdForPromptCompleter, @@ -22,10 +23,10 @@ import { */ export const registerEmbeddedResourcePrompt = (server: McpServer) => { // Prompt arguments - const promptArgsSchema = { + const promptArgsSchema = z.object({ resourceType: resourceTypeCompleter, resourceId: resourceIdForPromptCompleter, - }; + }); // Register the prompt server.registerPrompt( diff --git a/src/everything/prompts/simple.ts b/src/everything/prompts/simple.ts index e194a8361f..13d20d6667 100644 --- a/src/everything/prompts/simple.ts +++ b/src/everything/prompts/simple.ts @@ -1,4 +1,4 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from "@modelcontextprotocol/server"; /** * Register a simple prompt with no arguments diff --git a/src/everything/resources/files.ts b/src/everything/resources/files.ts index abd6dac7f8..8e051ef362 100644 --- a/src/everything/resources/files.ts +++ b/src/everything/resources/files.ts @@ -1,4 +1,4 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from "@modelcontextprotocol/server"; import { dirname, join } from "path"; import { fileURLToPath } from "url"; import { readdirSync, readFileSync, statSync } from "fs"; diff --git a/src/everything/resources/index.ts b/src/everything/resources/index.ts index 30c6f7dcf8..b7ec741376 100644 --- a/src/everything/resources/index.ts +++ b/src/everything/resources/index.ts @@ -1,4 +1,4 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from "@modelcontextprotocol/server"; import { registerResourceTemplates } from "./templates.js"; import { registerFileResources } from "./files.js"; import { fileURLToPath } from "url"; diff --git a/src/everything/resources/session.ts b/src/everything/resources/session.ts index db0e02c259..d3c7dbc05c 100644 --- a/src/everything/resources/session.ts +++ b/src/everything/resources/session.ts @@ -1,8 +1,9 @@ import { McpServer, RegisteredResource, -} from "@modelcontextprotocol/sdk/server/mcp.js"; -import { Resource, ResourceLink } from "@modelcontextprotocol/sdk/types.js"; + Resource, + ResourceLink, +} from "@modelcontextprotocol/server"; /** * Tracks registered session resources per server, by URI, to allow updating/removing on diff --git a/src/everything/resources/subscriptions.ts b/src/everything/resources/subscriptions.ts index 295e186e4a..510254ed00 100644 --- a/src/everything/resources/subscriptions.ts +++ b/src/everything/resources/subscriptions.ts @@ -1,8 +1,4 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { - SubscribeRequestSchema, - UnsubscribeRequestSchema, -} from "@modelcontextprotocol/sdk/types.js"; +import { McpServer } from "@modelcontextprotocol/server"; // Track subscriber session id lists by URI const subscriptions: Map> = new Map< @@ -36,13 +32,13 @@ const subsUpdateIntervals: Map = export const setSubscriptionHandlers = (server: McpServer) => { // Set the subscription handler server.server.setRequestHandler( - SubscribeRequestSchema, - async (request, extra) => { + "resources/subscribe", + async (request, ctx) => { // Get the URI to subscribe to const { uri } = request.params; // Get the session id (can be undefined for stdio) - const sessionId = extra.sessionId as string; + const sessionId = ctx.sessionId as string; // Acknowledge the subscribe request await server.sendLoggingMessage( @@ -67,13 +63,13 @@ export const setSubscriptionHandlers = (server: McpServer) => { // Set the unsubscription handler server.server.setRequestHandler( - UnsubscribeRequestSchema, - async (request, extra) => { + "resources/unsubscribe", + async (request, ctx) => { // Get the URI to subscribe to const { uri } = request.params; // Get the session id (can be undefined for stdio) - const sessionId = extra.sessionId as string; + const sessionId = ctx.sessionId as string; // Acknowledge the subscribe request await server.sendLoggingMessage( diff --git a/src/everything/resources/templates.ts b/src/everything/resources/templates.ts index ea1c484e8e..7a97aa0a48 100644 --- a/src/everything/resources/templates.ts +++ b/src/everything/resources/templates.ts @@ -3,8 +3,8 @@ import { CompleteResourceTemplateCallback, McpServer, ResourceTemplate, -} from "@modelcontextprotocol/sdk/server/mcp.js"; -import { completable } from "@modelcontextprotocol/sdk/server/completable.js"; + completable, +} from "@modelcontextprotocol/server"; // Resource types export const RESOURCE_TYPE_TEXT = "Text" as const; diff --git a/src/everything/server/index.ts b/src/everything/server/index.ts index 658077e3be..fcc0526aea 100644 --- a/src/everything/server/index.ts +++ b/src/everything/server/index.ts @@ -1,8 +1,4 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { - InMemoryTaskStore, - InMemoryTaskMessageQueue, -} from "@modelcontextprotocol/sdk/experimental/tasks"; +import { McpServer } from "@modelcontextprotocol/server"; import { setSubscriptionHandlers, stopSimulatedResourceUpdates, @@ -38,10 +34,6 @@ export const createServer: () => ServerFactoryResponse = () => { // Read the server instructions const instructions = readInstructions(); - // Create task store and message queue for task support - const taskStore = new InMemoryTaskStore(); - const taskMessageQueue = new InMemoryTaskMessageQueue(); - let initializeTimeout: NodeJS.Timeout | null = null; // Create the server @@ -64,19 +56,11 @@ export const createServer: () => ServerFactoryResponse = () => { listChanged: true, }, logging: {}, - tasks: { - list: {}, - cancel: {}, - requests: { - tools: { - call: {}, - }, - }, - }, + // No `tasks` capability: SDK v2 removed the experimental tasks layer + // (SEP-2663). Part 5 (#4852) brings tasks back on the + // `io.modelcontextprotocol/tasks` extension. }, instructions, - taskStore, - taskMessageQueue, }, ); @@ -113,8 +97,6 @@ export const createServer: () => ServerFactoryResponse = () => { stopSimulatedLogging(sessionId); stopSimulatedResourceUpdates(sessionId); removeSubscriber(sessionId); - // Clean up task store timers - taskStore.cleanup(); if (initializeTimeout) clearTimeout(initializeTimeout); }, } satisfies ServerFactoryResponse; diff --git a/src/everything/server/logging.ts b/src/everything/server/logging.ts index 75d801c0f0..3415912982 100644 --- a/src/everything/server/logging.ts +++ b/src/everything/server/logging.ts @@ -1,5 +1,4 @@ -import { LoggingLevel } from "@modelcontextprotocol/sdk/types.js"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { LoggingLevel, McpServer } from "@modelcontextprotocol/server"; // Map session ID to the interval for sending logging messages to the client const logsUpdateIntervals: Map = diff --git a/src/everything/server/roots.ts b/src/everything/server/roots.ts index 83c616c35c..cd4824c030 100644 --- a/src/everything/server/roots.ts +++ b/src/everything/server/roots.ts @@ -1,8 +1,4 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { - Root, - RootsListChangedNotificationSchema, -} from "@modelcontextprotocol/sdk/types.js"; +import { McpServer, Root } from "@modelcontextprotocol/server"; // Track roots by session id export const roots: Map = new Map< @@ -66,7 +62,7 @@ export const syncRoots = async (server: McpServer, sessionId?: string) => { } catch (error) { console.error( `Failed to request roots from client ${sessionId}: ${ - /* v8 ignore start -- the SDK always rejects listRoots() with an Error (McpError) */ + /* v8 ignore start -- the SDK always rejects listRoots() with an Error (ProtocolError or SdkError) */ error instanceof Error ? error.message : String(error) /* v8 ignore stop */ }`, @@ -79,7 +75,7 @@ export const syncRoots = async (server: McpServer, sessionId?: string) => { if (!roots.has(sessionId)) { // Set the list changed notification handler server.server.setNotificationHandler( - RootsListChangedNotificationSchema, + "notifications/roots/list_changed", requestRoots, ); diff --git a/src/everything/tools/echo.ts b/src/everything/tools/echo.ts index 0658e83195..8ae7943ad5 100644 --- a/src/everything/tools/echo.ts +++ b/src/everything/tools/echo.ts @@ -1,5 +1,4 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { McpServer, CallToolResult } from "@modelcontextprotocol/server"; import { z } from "zod"; // Tool input schema diff --git a/src/everything/tools/get-annotated-message.ts b/src/everything/tools/get-annotated-message.ts index eaff07b196..b339998286 100644 --- a/src/everything/tools/get-annotated-message.ts +++ b/src/everything/tools/get-annotated-message.ts @@ -1,5 +1,4 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { McpServer, CallToolResult } from "@modelcontextprotocol/server"; import { z } from "zod"; import { MCP_TINY_IMAGE } from "./get-tiny-image.js"; diff --git a/src/everything/tools/get-env.ts b/src/everything/tools/get-env.ts index cd67ab6720..4d16a2a954 100644 --- a/src/everything/tools/get-env.ts +++ b/src/everything/tools/get-env.ts @@ -1,5 +1,4 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { McpServer, CallToolResult } from "@modelcontextprotocol/server"; // Tool configuration const name = "get-env"; diff --git a/src/everything/tools/get-resource-links.ts b/src/everything/tools/get-resource-links.ts index 7684cb64a5..e35bf83631 100644 --- a/src/everything/tools/get-resource-links.ts +++ b/src/everything/tools/get-resource-links.ts @@ -1,6 +1,5 @@ import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { McpServer, CallToolResult } from "@modelcontextprotocol/server"; import { textResource, textResourceUri, diff --git a/src/everything/tools/get-resource-reference.ts b/src/everything/tools/get-resource-reference.ts index 5300aaf34a..f88cb5d887 100644 --- a/src/everything/tools/get-resource-reference.ts +++ b/src/everything/tools/get-resource-reference.ts @@ -1,6 +1,5 @@ import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { McpServer, CallToolResult } from "@modelcontextprotocol/server"; import { textResource, textResourceUri, diff --git a/src/everything/tools/get-roots-list.ts b/src/everything/tools/get-roots-list.ts index d7ad7fc46d..a18d208143 100644 --- a/src/everything/tools/get-roots-list.ts +++ b/src/everything/tools/get-roots-list.ts @@ -1,5 +1,4 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { McpServer, CallToolResult } from "@modelcontextprotocol/server"; import { syncRoots } from "../server/roots.js"; // Tool configuration @@ -44,9 +43,9 @@ export const registerGetRootsListTool = (server: McpServer) => { server.registerTool( name, config, - async (args, extra): Promise => { + async (args, ctx): Promise => { // Get the current rootsFetch the current roots list from the client if need be - const currentRoots = await syncRoots(server, extra.sessionId); + const currentRoots = await syncRoots(server, ctx.sessionId); // Respond if client supports roots but doesn't have any configured if ( diff --git a/src/everything/tools/get-structured-content.ts b/src/everything/tools/get-structured-content.ts index f9bde5c9e4..11f323e14b 100644 --- a/src/everything/tools/get-structured-content.ts +++ b/src/everything/tools/get-structured-content.ts @@ -1,9 +1,9 @@ import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { + McpServer, CallToolResult, ContentBlock, -} from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/server"; // Tool input schema const GetStructuredContentInputSchema = { diff --git a/src/everything/tools/get-sum.ts b/src/everything/tools/get-sum.ts index 470293bf6f..d8bd04234e 100644 --- a/src/everything/tools/get-sum.ts +++ b/src/everything/tools/get-sum.ts @@ -1,6 +1,5 @@ import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { McpServer, CallToolResult } from "@modelcontextprotocol/server"; // Tool input schema const GetSumSchema = z.object({ diff --git a/src/everything/tools/get-tiny-image.ts b/src/everything/tools/get-tiny-image.ts index f0f2cf517e..cb931cf941 100644 --- a/src/everything/tools/get-tiny-image.ts +++ b/src/everything/tools/get-tiny-image.ts @@ -1,5 +1,4 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { McpServer, CallToolResult } from "@modelcontextprotocol/server"; // A tiny encoded MCP logo image export const MCP_TINY_IMAGE = diff --git a/src/everything/tools/gzip-file-as-resource.ts b/src/everything/tools/gzip-file-as-resource.ts index c0cc0c56f8..99c9da1bb9 100644 --- a/src/everything/tools/gzip-file-as-resource.ts +++ b/src/everything/tools/gzip-file-as-resource.ts @@ -1,6 +1,9 @@ import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult, Resource } from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + CallToolResult, + Resource, +} from "@modelcontextprotocol/server"; import { gzipSync } from "node:zlib"; import { getSessionResourceURI, diff --git a/src/everything/tools/index.ts b/src/everything/tools/index.ts index 0272126eea..3e71f17740 100644 --- a/src/everything/tools/index.ts +++ b/src/everything/tools/index.ts @@ -1,4 +1,4 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from "@modelcontextprotocol/server"; import { registerGetAnnotatedMessageTool } from "./get-annotated-message.js"; import { registerEchoTool } from "./echo.js"; import { registerGetEnvTool } from "./get-env.js"; @@ -14,9 +14,6 @@ import { registerToggleSubscriberUpdatesTool } from "./toggle-subscriber-updates import { registerTriggerElicitationRequestTool } from "./trigger-elicitation-request.js"; import { registerTriggerLongRunningOperationTool } from "./trigger-long-running-operation.js"; import { registerTriggerSamplingRequestTool } from "./trigger-sampling-request.js"; -import { registerTriggerSamplingRequestAsyncTool } from "./trigger-sampling-request-async.js"; -import { registerTriggerElicitationRequestAsyncTool } from "./trigger-elicitation-request-async.js"; -import { registerSimulateResearchQueryTool } from "./simulate-research-query.js"; import { registerTriggerUrlElicitationTool } from "./trigger-url-elicitation.js"; /** @@ -47,9 +44,8 @@ export const registerConditionalTools = (server: McpServer) => { registerTriggerElicitationRequestTool(server); registerTriggerUrlElicitationTool(server); registerTriggerSamplingRequestTool(server); - // Task-based research tool (uses experimental tasks API) - registerSimulateResearchQueryTool(server); - // Bidirectional task tools - server sends requests that client executes as tasks - registerTriggerSamplingRequestAsyncTool(server); - registerTriggerElicitationRequestAsyncTool(server); + // The task tools (simulate-research-query, trigger-sampling-request-async, + // trigger-elicitation-request-async) were removed with SDK v2, which drops + // the experimental tasks layer (SEP-2663). Part 5 (#4852) re-implements them + // on the `io.modelcontextprotocol/tasks` extension. }; diff --git a/src/everything/tools/simulate-research-query.ts b/src/everything/tools/simulate-research-query.ts deleted file mode 100644 index 7055d3db5e..0000000000 --- a/src/everything/tools/simulate-research-query.ts +++ /dev/null @@ -1,348 +0,0 @@ -import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { - CallToolResult, - GetTaskResult, - Task, - ElicitResult, - ElicitResultSchema, -} from "@modelcontextprotocol/sdk/types.js"; -import { CreateTaskResult } from "@modelcontextprotocol/sdk/experimental/tasks"; - -// Tool input schema -const SimulateResearchQuerySchema = z.object({ - topic: z.string().describe("The research topic to investigate"), - ambiguous: z - .boolean() - .default(false) - .describe( - "Simulate an ambiguous query that requires clarification (triggers input_required status)", - ), -}); - -// Research stages -const STAGES = [ - "Gathering sources", - "Analyzing content", - "Synthesizing findings", - "Generating report", -]; - -// Duration per stage in milliseconds -const STAGE_DURATION = 1000; - -// Internal state for tracking research tasks -interface ResearchState { - topic: string; - ambiguous: boolean; - currentStage: number; - clarification?: string; - completed: boolean; - result?: CallToolResult; -} - -// Map to store research state per task -const researchStates = new Map(); - -/** - * Runs the background research process. - * Updates task status as it progresses through stages. - * If clarification is needed, sends elicitation via sendRequest with relatedTask, - * which queues the request in the task message queue. The SDK delivers it through - * the tasks/result stream when the client calls tasks/result (per spec input_required flow). - * This works on all transports (STDIO, SSE, Streamable HTTP). - */ -async function runResearchProcess( - taskId: string, - args: z.infer, - taskStore: { - updateTaskStatus: ( - taskId: string, - status: Task["status"], - message?: string, - ) => Promise; - storeTaskResult: ( - taskId: string, - status: "completed" | "failed", - result: CallToolResult, - ) => Promise; - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - sendRequest: any, -): Promise { - const state = researchStates.get(taskId); - /* v8 ignore next -- createTask stores the state immediately before starting this process */ - if (!state) return; - - // Process each stage - for (let i = state.currentStage; i < STAGES.length; i++) { - state.currentStage = i; - - // Check if task was cancelled externally - if (state.completed) return; - - // Update status message for current stage - await taskStore.updateTaskStatus(taskId, "working", `${STAGES[i]}...`); - - // At synthesis stage (index 2), check if clarification is needed - if (i === 2 && state.ambiguous && !state.clarification) { - // Update status to show we're requesting input (spec SHOULD) - await taskStore.updateTaskStatus( - taskId, - "input_required", - `Found multiple interpretations for "${state.topic}". Requesting clarification...`, - ); - - try { - // relatedTask queues elicitation via task message queue → delivered through tasks/result on all transports - const elicitResult: ElicitResult = await sendRequest( - { - method: "elicitation/create", - params: { - message: `The research query "${state.topic}" could have multiple interpretations. Please clarify what you're looking for:`, - requestedSchema: { - type: "object", - properties: { - interpretation: { - type: "string", - title: "Clarification", - description: - "Which interpretation of the topic do you mean?", - oneOf: getInterpretationsForTopic(state.topic), - }, - }, - required: ["interpretation"], - }, - }, - }, - ElicitResultSchema, - { relatedTask: { taskId } }, - ); - - // Process elicitation response - if (elicitResult.action === "accept" && elicitResult.content) { - state.clarification = - (elicitResult.content as { interpretation?: string }) - .interpretation || "User accepted without selection"; - } else if (elicitResult.action === "decline") { - state.clarification = "User declined - using default interpretation"; - } else { - state.clarification = "User cancelled - using default interpretation"; - } - } catch (error) { - // Elicitation failed - use default interpretation and continue - console.warn( - `Elicitation failed for task ${taskId}:`, - error instanceof Error ? error.message : String(error), - ); - state.clarification = "technical (default - elicitation unavailable)"; - } - - // Resume with working status (spec SHOULD) - await taskStore.updateTaskStatus( - taskId, - "working", - `Continuing with interpretation: "${state.clarification}"...`, - ); - - // Continue processing (no return - just keep going through the loop) - } - - // Simulate work for this stage - await new Promise((resolve) => setTimeout(resolve, STAGE_DURATION)); - } - - // All stages complete - generate result - state.completed = true; - const result = generateResearchReport(state); - state.result = result; - - await taskStore.storeTaskResult(taskId, "completed", result); -} - -/** - * Generates the final research report with educational content about tasks. - */ -function generateResearchReport(state: ResearchState): CallToolResult { - const topic = state.clarification - ? `${state.topic} (${state.clarification})` - : state.topic; - - const report = `# Research Report: ${topic} - -## Research Parameters -- **Topic**: ${state.topic} -${state.clarification ? `- **Clarification**: ${state.clarification}` : ""} - -## Synthesis -This research query was processed through ${STAGES.length} stages: -${STAGES.map((s, i) => `- Stage ${i + 1}: ${s} ✓`).join("\n")} - ---- - -## About This Demo (SEP-1686: Tasks) - -This tool demonstrates MCP's task-based execution pattern for long-running operations: - -**Task Lifecycle Demonstrated:** -1. \`tools/call\` with \`task\` parameter → Server returns \`CreateTaskResult\` (not the final result) -2. Client polls \`tasks/get\` → Server returns current status and \`statusMessage\` -3. Status progressed: \`working\` → ${ - state.clarification ? `\`input_required\` → \`working\` → ` : "" - }\`completed\` -4. Client calls \`tasks/result\` → Server returns this final result - -${ - state.clarification - ? `**Elicitation Flow:** -When the query was ambiguous, the server sent an \`elicitation/create\` request -to the client. The task status changed to \`input_required\` while awaiting user input. -${ - state.clarification.includes("unavailable") - ? `**Note:** Elicitation failed and a default interpretation was used.` - : `After receiving clarification ("${state.clarification}"), the task resumed processing and completed.` -} -` - : "" -} -**Key Concepts:** -- Tasks enable "call now, fetch later" patterns -- \`statusMessage\` provides human-readable progress updates -- Tasks have TTL (time-to-live) for automatic cleanup -- \`pollInterval\` suggests how often to check status -- Elicitation requests use \`relatedTask\` to queue via tasks/result (works on all transports) - -*This is a simulated research report from the Everything MCP Server.* -`; - - return { - content: [ - { - type: "text", - text: report, - }, - ], - }; -} - -/** - * Registers the 'simulate-research-query' tool as a task-based tool. - * - * This tool demonstrates the MCP Tasks feature (SEP-1686) with a real-world scenario: - * a research tool that gathers and synthesizes information from multiple sources. - * If the query is ambiguous, it pauses to ask for clarification before completing. - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - */ -export const registerSimulateResearchQueryTool = (server: McpServer) => { - // Check if client supports elicitation (needed for input_required flow) - const clientCapabilities = server.server.getClientCapabilities() || {}; - const clientSupportsElicitation: boolean = - clientCapabilities.elicitation !== undefined; - - server.experimental.tasks.registerToolTask( - "simulate-research-query", - { - title: "Simulate Research Query", - description: - "Simulates a deep research operation that gathers, analyzes, and synthesizes information. " + - "Demonstrates MCP task-based operations with progress through multiple stages. " + - "If 'ambiguous' is true and client supports elicitation, sends an elicitation request for clarification.", - inputSchema: SimulateResearchQuerySchema, - execution: { taskSupport: "required" }, - annotations: { - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: false, - }, - }, - { - /** - * Creates a new research task and starts background processing. - */ - createTask: async (args, extra): Promise => { - const validatedArgs = SimulateResearchQuerySchema.parse(args); - - // Create the task in the store - const task = await extra.taskStore.createTask({ - ttl: 300000, // 5 minutes - pollInterval: 1000, - }); - - // Initialize research state - const state: ResearchState = { - topic: validatedArgs.topic, - ambiguous: validatedArgs.ambiguous && clientSupportsElicitation, - currentStage: 0, - completed: false, - }; - researchStates.set(task.taskId, state); - - // Start background research (don't await - runs asynchronously) - // Pass sendRequest for elicitation (queued via task message queue, works on all transports) - runResearchProcess( - task.taskId, - validatedArgs, - extra.taskStore, - extra.sendRequest, - ).catch((error) => { - console.error(`Research task ${task.taskId} failed:`, error); - extra.taskStore - .updateTaskStatus(task.taskId, "failed", String(error)) - .catch(console.error); - }); - - return { task }; - }, - - /* v8 ignore start -- SDK 1.x never calls a tool task's getTask/getTaskResult: tasks/get and tasks/result are answered from the task store (Protocol), so these handlers are unreachable over the wire */ - /** - * Returns the current status of the research task. - */ - getTask: async (args, extra): Promise => { - return await extra.taskStore.getTask(extra.taskId); - }, - - /** - * Returns the task result. - * Elicitation is now handled directly in the background process. - */ - getTaskResult: async (args, extra): Promise => { - // Return the stored result - const result = await extra.taskStore.getTaskResult(extra.taskId); - - // Clean up state - researchStates.delete(extra.taskId); - - return result as CallToolResult; - }, - /* v8 ignore stop */ - }, - ); -}; - -/** - * Returns contextual interpretation options based on the topic. - */ -function getInterpretationsForTopic( - topic: string, -): Array<{ const: string; title: string }> { - const lowerTopic = topic.toLowerCase(); - - // Example: contextual interpretations for "python" - if (lowerTopic.includes("python")) { - return [ - { const: "programming", title: "Python programming language" }, - { const: "snake", title: "Python snake species" }, - { const: "comedy", title: "Monty Python comedy group" }, - ]; - } - - // Default generic interpretations - return [ - { const: "technical", title: "Technical/scientific perspective" }, - { const: "historical", title: "Historical perspective" }, - { const: "current", title: "Current events/news perspective" }, - ]; -} diff --git a/src/everything/tools/toggle-simulated-logging.ts b/src/everything/tools/toggle-simulated-logging.ts index 7551da0967..a847e6459a 100644 --- a/src/everything/tools/toggle-simulated-logging.ts +++ b/src/everything/tools/toggle-simulated-logging.ts @@ -1,5 +1,4 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { McpServer, CallToolResult } from "@modelcontextprotocol/server"; import { beginSimulatedLogging, stopSimulatedLogging, @@ -38,8 +37,8 @@ export const registerToggleSimulatedLoggingTool = (server: McpServer) => { server.registerTool( name, config, - async (_args, extra): Promise => { - const sessionId = extra?.sessionId; + async (_args, ctx): Promise => { + const sessionId = ctx?.sessionId; let response: string; if (clients.has(sessionId)) { diff --git a/src/everything/tools/toggle-subscriber-updates.ts b/src/everything/tools/toggle-subscriber-updates.ts index 4b7f3b1c1c..2b53fded57 100644 --- a/src/everything/tools/toggle-subscriber-updates.ts +++ b/src/everything/tools/toggle-subscriber-updates.ts @@ -1,5 +1,4 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { McpServer, CallToolResult } from "@modelcontextprotocol/server"; import { beginSimulatedResourceUpdates, stopSimulatedResourceUpdates, @@ -41,8 +40,8 @@ export const registerToggleSubscriberUpdatesTool = (server: McpServer) => { server.registerTool( name, config, - async (_args, extra): Promise => { - const sessionId = extra?.sessionId; + async (_args, ctx): Promise => { + const sessionId = ctx?.sessionId; let response: string; if (clients.has(sessionId)) { diff --git a/src/everything/tools/trigger-elicitation-request-async.ts b/src/everything/tools/trigger-elicitation-request-async.ts deleted file mode 100644 index 9578007d9f..0000000000 --- a/src/everything/tools/trigger-elicitation-request-async.ts +++ /dev/null @@ -1,295 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { z } from "zod"; - -// Tool configuration -const name = "trigger-elicitation-request-async"; -const config = { - title: "Trigger Async Elicitation Request Tool", - description: - "Trigger an async elicitation request that the CLIENT executes as a background task. " + - "Demonstrates bidirectional MCP tasks where the server sends an elicitation request and " + - "the client handles user input asynchronously, allowing the server to poll for completion.", - inputSchema: {}, - annotations: { - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: false, - }, -}; - -// Poll interval in milliseconds -const POLL_INTERVAL = 1000; - -// Time-to-live requested for the client's task (10 minutes for user input) -const TASK_TTL = 600000; - -// Stop polling this long before the TTL runs out, so the last tasks/get -// reaches the client while it still holds the task -const TTL_SAFETY_MARGIN = 5000; - -/** - * Registers the 'trigger-elicitation-request-async' tool. - * - * This tool demonstrates bidirectional MCP tasks for elicitation: - * - Server sends elicitation request to client with task metadata - * - Client creates a task and returns CreateTaskResult - * - Client prompts user for input (task status: input_required) - * - Server polls client's tasks/get endpoint for status - * - Server fetches final result from client's tasks/result endpoint - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - */ -export const registerTriggerElicitationRequestAsyncTool = ( - server: McpServer, -) => { - // Check client capabilities - const clientCapabilities = server.server.getClientCapabilities() || {}; - - // Client must support form-mode elicitation AND tasks.requests.elicitation. - // The request is form mode, so a URL-only client cannot answer it (#4985): - // form mode is `elicitation.form`, or `elicitation` with neither mode. - const elicitation = clientCapabilities.elicitation; - const clientSupportsElicitation = - elicitation !== undefined && - (elicitation.form !== undefined || elicitation.url === undefined); - const clientTasksCapability = clientCapabilities.tasks as - | { - requests?: { elicitation?: { create?: object } }; - } - | undefined; - const clientSupportsAsyncElicitation = - clientTasksCapability?.requests?.elicitation?.create !== undefined; - - if (clientSupportsElicitation && clientSupportsAsyncElicitation) { - server.registerTool( - name, - config, - async (args, extra): Promise => { - // Polling must end before the client may expire the task. The client - // starts the TTL when it creates the task, after this point, so a - // deadline measured from here is conservative. performance.now() is - // monotonic, so a wall-clock adjustment cannot stretch the deadline. - const pollDeadline = performance.now() + TASK_TTL - TTL_SAFETY_MARGIN; - - // Create the elicitation request WITH task metadata - // Using z.any() schema to avoid complex type matching with _meta - const request = { - method: "elicitation/create" as const, - params: { - task: { - ttl: TASK_TTL, // 10 minutes (user input may take a while) - }, - message: - "Please provide inputs for the following fields (async task demo):", - requestedSchema: { - type: "object" as const, - properties: { - name: { - title: "Your Name", - type: "string" as const, - description: "Your full name", - }, - favoriteColor: { - title: "Favorite Color", - type: "string" as const, - description: "What is your favorite color?", - enum: ["Red", "Blue", "Green", "Yellow", "Purple"], - }, - agreeToTerms: { - title: "Terms Agreement", - type: "boolean" as const, - description: "Do you agree to the terms and conditions?", - }, - }, - required: ["name"], - }, - }, - }; - - // Send the elicitation request - // Client may return either: - // - ElicitResult (synchronous execution) - // - CreateTaskResult (task-based execution with { task } object) - const elicitResponse = await extra.sendRequest( - request as Parameters[0], - z.union([ - // CreateTaskResult - client created a task - z.object({ - task: z.object({ - taskId: z.string(), - status: z.string(), - pollInterval: z.number().optional(), - statusMessage: z.string().optional(), - }), - }), - // ElicitResult - synchronous execution - z.object({ - action: z.string(), - content: z.any().optional(), - }), - ]), - ); - - // Check if client returned CreateTaskResult (has task object) - const isTaskResult = "task" in elicitResponse && elicitResponse.task; - if (!isTaskResult) { - // Client executed synchronously - return the direct response - return { - content: [ - { - type: "text", - text: `[SYNC] Client executed synchronously:\n${JSON.stringify( - elicitResponse, - null, - 2, - )}`, - }, - ], - }; - } - - const taskId = elicitResponse.task.taskId; - const statusMessages: string[] = []; - statusMessages.push(`Task created: ${taskId}`); - - // Poll for task completion - let attempts = 0; - let taskStatus = elicitResponse.task.status; - // A task that is already finished when created is never polled, so - // its status message has to come from the CreateTaskResult - let taskStatusMessage = elicitResponse.task.statusMessage; - let timedOut = false; - - while ( - taskStatus !== "completed" && - taskStatus !== "failed" && - taskStatus !== "cancelled" - ) { - // Wait before polling - await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL)); - - // Give up rather than poll past the TTL the task was created with. - // Checked after the wait, so a timer that fires late cannot slip a - // poll in after the deadline. - if (performance.now() > pollDeadline) { - timedOut = true; - break; - } - attempts++; - - // Get task status from client - const pollResult = await extra.sendRequest( - { - method: "tasks/get", - params: { taskId }, - }, - z.looseObject({ - status: z.string(), - statusMessage: z.string().optional(), - }), - ); - - taskStatus = pollResult.status; - taskStatusMessage = pollResult.statusMessage; - - // Only log status changes or every 10 polls to avoid spam - if ( - attempts === 1 || - attempts % 10 === 0 || - taskStatus !== "input_required" - ) { - statusMessages.push( - `Poll ${attempts}: ${taskStatus}${ - taskStatusMessage ? ` - ${taskStatusMessage}` : "" - }`, - ); - } - } - - // Check for timeout - if (timedOut) { - return { - content: [ - { - type: "text", - text: `[TIMEOUT] Task timed out after ${attempts} poll attempts, before its ${ - TASK_TTL / 60000 - }-minute TTL expired\n\nProgress:\n${statusMessages.join( - "\n", - )}`, - }, - ], - }; - } - - // Check for failure/cancellation - if (taskStatus === "failed" || taskStatus === "cancelled") { - return { - content: [ - { - type: "text", - text: `[${taskStatus.toUpperCase()}] ${ - taskStatusMessage || "No message" - }\n\nProgress:\n${statusMessages.join("\n")}`, - }, - ], - }; - } - - // Fetch the final result - const result = await extra.sendRequest( - { - method: "tasks/result", - params: { taskId }, - }, - z.any(), - ); - - // Format the elicitation result - const content: CallToolResult["content"] = []; - - if (result.action === "accept" && result.content) { - content.push({ - type: "text", - text: `[COMPLETED] User provided the requested information!`, - }); - - const userData = result.content as Record; - const lines = []; - if (userData.name) lines.push(`- Name: ${userData.name}`); - if (userData.favoriteColor) - lines.push(`- Favorite Color: ${userData.favoriteColor}`); - if (userData.agreeToTerms !== undefined) - lines.push(`- Agreed to terms: ${userData.agreeToTerms}`); - - content.push({ - type: "text", - text: `User inputs:\n${lines.join("\n")}`, - }); - } else if (result.action === "decline") { - content.push({ - type: "text", - text: `[DECLINED] User declined to provide the requested information.`, - }); - } else if (result.action === "cancel") { - content.push({ - type: "text", - text: `[CANCELLED] User cancelled the elicitation dialog.`, - }); - } - - // Include progress and raw result for debugging - content.push({ - type: "text", - text: `\nProgress:\n${statusMessages.join( - "\n", - )}\n\nRaw result: ${JSON.stringify(result, null, 2)}`, - }); - - return { content }; - }, - ); - } -}; diff --git a/src/everything/tools/trigger-elicitation-request.ts b/src/everything/tools/trigger-elicitation-request.ts index 23854db1b9..2df4500d93 100644 --- a/src/everything/tools/trigger-elicitation-request.ts +++ b/src/everything/tools/trigger-elicitation-request.ts @@ -1,8 +1,4 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { - ElicitResultSchema, - CallToolResult, -} from "@modelcontextprotocol/sdk/types.js"; +import { McpServer, CallToolResult } from "@modelcontextprotocol/server"; // Tool configuration const name = "trigger-elicitation-request"; @@ -53,8 +49,8 @@ export const registerTriggerElicitationRequestTool = (server: McpServer) => { server.registerTool( name, config, - async (args, extra): Promise => { - const elicitationResult = await extra.sendRequest( + async (args, ctx): Promise => { + const elicitationResult = await ctx.mcpReq.send( { method: "elicitation/create", params: { @@ -179,7 +175,6 @@ export const registerTriggerElicitationRequestTool = (server: McpServer) => { }, }, }, - ElicitResultSchema, { timeout: 10 * 60 * 1000 /* 10 minutes */ }, ); diff --git a/src/everything/tools/trigger-long-running-operation.ts b/src/everything/tools/trigger-long-running-operation.ts index 5bbb8fc517..b1151d25c2 100644 --- a/src/everything/tools/trigger-long-running-operation.ts +++ b/src/everything/tools/trigger-long-running-operation.ts @@ -1,6 +1,5 @@ import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { McpServer, CallToolResult } from "@modelcontextprotocol/server"; // Tool input schema const TriggerLongRunningOperationSchema = z.object({ @@ -43,11 +42,11 @@ export const registerTriggerLongRunningOperationTool = (server: McpServer) => { server.registerTool( name, config, - async (args, extra): Promise => { + async (args, ctx): Promise => { const validatedArgs = TriggerLongRunningOperationSchema.parse(args); const { duration, steps } = validatedArgs; const stepDuration = duration / steps; - const progressToken = extra._meta?.progressToken; + const progressToken = ctx.mcpReq._meta?.progressToken; for (let i = 1; i < steps + 1; i++) { await new Promise((resolve) => @@ -64,7 +63,7 @@ export const registerTriggerLongRunningOperationTool = (server: McpServer) => { progressToken, }, }, - { relatedRequestId: extra.requestId }, + { relatedRequestId: ctx.mcpReq.id }, ); } } diff --git a/src/everything/tools/trigger-sampling-request-async.ts b/src/everything/tools/trigger-sampling-request-async.ts deleted file mode 100644 index 4236328209..0000000000 --- a/src/everything/tools/trigger-sampling-request-async.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { - CallToolResult, - CreateMessageRequest, -} from "@modelcontextprotocol/sdk/types.js"; -import { z } from "zod"; - -// Tool input schema -const TriggerSamplingRequestAsyncSchema = z.object({ - prompt: z.string().describe("The prompt to send to the LLM"), - maxTokens: z - .number() - .default(100) - .describe("Maximum number of tokens to generate"), -}); - -// Tool configuration -const name = "trigger-sampling-request-async"; -const config = { - title: "Trigger Async Sampling Request Tool", - description: - "Trigger an async sampling request that the CLIENT executes as a background task. " + - "Demonstrates bidirectional MCP tasks where the server sends a request and the client " + - "executes it asynchronously, allowing the server to poll for progress and results.", - inputSchema: TriggerSamplingRequestAsyncSchema, - annotations: { - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, -}; - -// Poll interval in milliseconds -const POLL_INTERVAL = 1000; - -// Maximum poll attempts before timeout -const MAX_POLL_ATTEMPTS = 60; - -/** - * Registers the 'trigger-sampling-request-async' tool. - * - * This tool demonstrates bidirectional MCP tasks: - * - Server sends sampling request to client with task metadata - * - Client creates a task and returns CreateTaskResult - * - Server polls client's tasks/get endpoint for status - * - Server fetches final result from client's tasks/result endpoint - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - */ -export const registerTriggerSamplingRequestAsyncTool = (server: McpServer) => { - // Check client capabilities - const clientCapabilities = server.server.getClientCapabilities() || {}; - - // Client must support sampling AND tasks.requests.sampling - const clientSupportsSampling = clientCapabilities.sampling !== undefined; - const clientTasksCapability = clientCapabilities.tasks as - | { - requests?: { sampling?: { createMessage?: object } }; - } - | undefined; - const clientSupportsAsyncSampling = - clientTasksCapability?.requests?.sampling?.createMessage !== undefined; - - if (clientSupportsSampling && clientSupportsAsyncSampling) { - server.registerTool( - name, - config, - async (args, extra): Promise => { - const validatedArgs = TriggerSamplingRequestAsyncSchema.parse(args); - const { prompt, maxTokens } = validatedArgs; - - // Create the sampling request WITH task metadata - // The params.task field signals to the client that this should be executed as a task - const request: CreateMessageRequest & { - params: { task?: { ttl: number } }; - } = { - method: "sampling/createMessage", - params: { - task: { - ttl: 300000, // 5 minutes - }, - messages: [ - { - role: "user", - content: { - type: "text", - text: `Resource ${name} context: ${prompt}`, - }, - }, - ], - systemPrompt: "You are a helpful test server.", - maxTokens, - temperature: 0.7, - }, - }; - - // Send the sampling request - // Client may return either: - // - CreateMessageResult (synchronous execution) - // - CreateTaskResult (task-based execution with { task } object) - const samplingResponse = await extra.sendRequest( - request, - z.union([ - // CreateTaskResult - client created a task - z.object({ - task: z.object({ - taskId: z.string(), - status: z.string(), - pollInterval: z.number().optional(), - statusMessage: z.string().optional(), - }), - }), - // CreateMessageResult - synchronous execution - z.object({ - role: z.string(), - content: z.any(), - model: z.string(), - stopReason: z.string().optional(), - }), - ]), - ); - - // Check if client returned CreateTaskResult (has task object) - const isTaskResult = - "task" in samplingResponse && samplingResponse.task; - if (!isTaskResult) { - // Client executed synchronously - return the direct response - return { - content: [ - { - type: "text", - text: `[SYNC] Client executed synchronously:\n${JSON.stringify( - samplingResponse, - null, - 2, - )}`, - }, - ], - }; - } - - const taskId = samplingResponse.task.taskId; - const statusMessages: string[] = []; - statusMessages.push(`Task created: ${taskId}`); - - // Poll for task completion - let attempts = 0; - let taskStatus = samplingResponse.task.status; - // A task that is already finished when created is never polled, so - // its status message has to come from the CreateTaskResult - let taskStatusMessage = samplingResponse.task.statusMessage; - - while ( - taskStatus !== "completed" && - taskStatus !== "failed" && - taskStatus !== "cancelled" && - attempts < MAX_POLL_ATTEMPTS - ) { - // Wait before polling - await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL)); - attempts++; - - // Get task status from client - const pollResult = await extra.sendRequest( - { - method: "tasks/get", - params: { taskId }, - }, - z.looseObject({ - status: z.string(), - statusMessage: z.string().optional(), - }), - ); - - taskStatus = pollResult.status; - taskStatusMessage = pollResult.statusMessage; - statusMessages.push( - `Poll ${attempts}: ${taskStatus}${ - taskStatusMessage ? ` - ${taskStatusMessage}` : "" - }`, - ); - } - - // Check for timeout - if (attempts >= MAX_POLL_ATTEMPTS) { - return { - content: [ - { - type: "text", - text: `[TIMEOUT] Task timed out after ${MAX_POLL_ATTEMPTS} poll attempts\n\nProgress:\n${statusMessages.join( - "\n", - )}`, - }, - ], - }; - } - - // Check for failure/cancellation - if (taskStatus === "failed" || taskStatus === "cancelled") { - return { - content: [ - { - type: "text", - text: `[${taskStatus.toUpperCase()}] ${ - taskStatusMessage || "No message" - }\n\nProgress:\n${statusMessages.join("\n")}`, - }, - ], - }; - } - - // Fetch the final result - const result = await extra.sendRequest( - { - method: "tasks/result", - params: { taskId }, - }, - z.any(), - ); - - // Return the result with status history - return { - content: [ - { - type: "text", - text: `[COMPLETED] Async sampling completed!\n\n**Progress:**\n${statusMessages.join( - "\n", - )}\n\n**Result:**\n${JSON.stringify(result, null, 2)}`, - }, - ], - }; - }, - ); - } -}; diff --git a/src/everything/tools/trigger-sampling-request.ts b/src/everything/tools/trigger-sampling-request.ts index e67a1ff0b7..28ed5fb4bd 100644 --- a/src/everything/tools/trigger-sampling-request.ts +++ b/src/everything/tools/trigger-sampling-request.ts @@ -1,9 +1,8 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { + McpServer, CallToolResult, CreateMessageRequest, - CreateMessageResultSchema, -} from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/server"; import { z } from "zod"; // Tool input schema @@ -53,7 +52,7 @@ export const registerTriggerSamplingRequestTool = (server: McpServer) => { server.registerTool( name, config, - async (args, extra): Promise => { + async (args, ctx): Promise => { const validatedArgs = TriggerSamplingRequestSchema.parse(args); const { prompt, maxTokens } = validatedArgs; @@ -77,10 +76,7 @@ export const registerTriggerSamplingRequestTool = (server: McpServer) => { }; // Send the sampling request to the client - const result = await extra.sendRequest( - request, - CreateMessageResultSchema, - ); + const result = await ctx.mcpReq.send(request); // Return the result to the client return { diff --git a/src/everything/tools/trigger-url-elicitation.ts b/src/everything/tools/trigger-url-elicitation.ts index cc3c4cf789..9faa1e557d 100644 --- a/src/everything/tools/trigger-url-elicitation.ts +++ b/src/everything/tools/trigger-url-elicitation.ts @@ -1,11 +1,10 @@ import { randomUUID } from "node:crypto"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { + McpServer, CallToolResult, ElicitRequestURLParams, - ElicitResultSchema, UrlElicitationRequiredError, -} from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/server"; import { z } from "zod"; // Tool input schema @@ -107,7 +106,7 @@ export const registerTriggerUrlElicitationTool = (server: McpServer) => { server.registerTool( name, config, - async (args, extra): Promise => { + async (args, ctx): Promise => { const { url, message, @@ -116,7 +115,7 @@ export const registerTriggerUrlElicitationTool = (server: McpServer) => { } = args; const elicitationId = requestedElicitationId ?? randomUUID(); - const sessionId = extra.sessionId ?? "default"; + const sessionId = ctx.sessionId ?? "default"; // Key the one-shot error-path marker on inputs the client resends // verbatim when it retries the original tool call. A real client retries @@ -170,12 +169,11 @@ export const registerTriggerUrlElicitationTool = (server: McpServer) => { } // Request path: send elicitation/create and await the user's response - const elicitationResult = await extra.sendRequest( + const elicitationResult = await ctx.mcpReq.send( { method: "elicitation/create", params: elicitationParams, }, - ElicitResultSchema, { timeout: 10 * 60 * 1000 /* 10 minutes */ }, ); diff --git a/src/everything/transports/in-memory-event-store.ts b/src/everything/transports/in-memory-event-store.ts index 88dec7b5be..80a270c58f 100644 --- a/src/everything/transports/in-memory-event-store.ts +++ b/src/everything/transports/in-memory-event-store.ts @@ -11,12 +11,12 @@ // tested directly. import { randomUUID } from "node:crypto"; -import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js"; import type { + JSONRPCMessage, EventId, EventStore, StreamId, -} from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +} from "@modelcontextprotocol/server"; export class InMemoryEventStore implements EventStore { private events: Map< diff --git a/src/everything/transports/sse.ts b/src/everything/transports/sse.ts index 1db484683a..a6a88761cb 100644 --- a/src/everything/transports/sse.ts +++ b/src/everything/transports/sse.ts @@ -5,7 +5,7 @@ // test can serve it on a free port and drive it with a real client, and each // app has its own session map. `startSseServer()` is what the launcher runs. -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; +import { SSEServerTransport } from "@modelcontextprotocol/server-legacy/sse"; import express, { type Express, type Response } from "express"; import type { Server } from "node:http"; import { createServer } from "../server/index.js"; diff --git a/src/everything/transports/stdio.ts b/src/everything/transports/stdio.ts index 61a36d71d6..312a89c110 100644 --- a/src/everything/transports/stdio.ts +++ b/src/everything/transports/stdio.ts @@ -9,7 +9,7 @@ // launches the binary. import type { Readable, Writable } from "node:stream"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import { createServer } from "../server/index.js"; /** What a started stdio server hands back: the server and its SIGINT handler. */ diff --git a/src/everything/transports/streamableHttp.ts b/src/everything/transports/streamableHttp.ts index 1057302a9e..9b475b3c20 100644 --- a/src/everything/transports/streamableHttp.ts +++ b/src/everything/transports/streamableHttp.ts @@ -7,8 +7,8 @@ // a real client, and call its shutdown handler directly; each app has its own // session map. `startStreamableHttpServer()` is what the launcher runs. -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; -import { isJSONRPCRequest } from "@modelcontextprotocol/sdk/types.js"; +import { isJSONRPCRequest } from "@modelcontextprotocol/server"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; import express, { Express, Request, Response } from "express"; import type { Server } from "node:http"; import { createServer } from "../server/index.js"; @@ -84,9 +84,9 @@ export function createApp(): StreamableHttpApp { ); // Map sessionId to server transport for each client - const transports: Map = new Map< + const transports: Map = new Map< string, - StreamableHTTPServerTransport + NodeStreamableHTTPServerTransport >(); // Handle POST requests for client messages @@ -96,7 +96,7 @@ export function createApp(): StreamableHttpApp { // Check for existing session ID const sessionId = req.headers["mcp-session-id"] as string | undefined; - let transport: StreamableHTTPServerTransport; + let transport: NodeStreamableHTTPServerTransport; if (sessionId && transports.has(sessionId)) { // Reuse existing transport @@ -106,7 +106,7 @@ export function createApp(): StreamableHttpApp { // New initialization request const eventStore = new InMemoryEventStore(); - transport = new StreamableHTTPServerTransport({ + transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), eventStore, // Enable resumability onsessioninitialized: (sessionId: string) => { diff --git a/src/filesystem/__tests__/bin-smoke.test.ts b/src/filesystem/__tests__/bin-smoke.test.ts index c16059188c..520cc0cc21 100644 --- a/src/filesystem/__tests__/bin-smoke.test.ts +++ b/src/filesystem/__tests__/bin-smoke.test.ts @@ -10,8 +10,8 @@ import { createRequire } from "node:module"; import fs from "node:fs/promises"; import path from "node:path"; import { describe, expect, it } from "vitest"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { StdioClientTransport } from "@modelcontextprotocol/client/stdio"; +import { Client } from "@modelcontextprotocol/client"; import { makeTempDir } from "./helpers.js"; const packageRoot = path.join(import.meta.dirname, ".."); diff --git a/src/filesystem/__tests__/helpers.ts b/src/filesystem/__tests__/helpers.ts index f251c1a426..5e7d6afff2 100644 --- a/src/filesystem/__tests__/helpers.ts +++ b/src/filesystem/__tests__/helpers.ts @@ -8,14 +8,12 @@ import fs from "fs/promises"; import os from "os"; import path from "path"; import { vi } from "vitest"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; -import { - ListRootsRequestSchema, - type CallToolResult, - type ClientCapabilities, - type Root, -} from "@modelcontextprotocol/sdk/types.js"; +import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; +import type { + CallToolResult, + ClientCapabilities, + Root, +} from "@modelcontextprotocol/client"; import { createServer } from "../server.js"; export interface Connected { @@ -42,7 +40,7 @@ export async function connect( ); const { listRoots } = options; if (options.capabilities?.roots && listRoots) { - client.setRequestHandler(ListRootsRequestSchema, async () => ({ + client.setRequestHandler("roots/list", async () => ({ roots: await listRoots(), })); } diff --git a/src/filesystem/__tests__/server-paths.test.ts b/src/filesystem/__tests__/server-paths.test.ts index a3061d1640..869991de8b 100644 --- a/src/filesystem/__tests__/server-paths.test.ts +++ b/src/filesystem/__tests__/server-paths.test.ts @@ -8,7 +8,7 @@ import fs from "fs/promises"; import os from "os"; import path from "path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import type { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import type { Client } from "@modelcontextprotocol/client"; import { call, connect, diff --git a/src/filesystem/__tests__/server-roots.test.ts b/src/filesystem/__tests__/server-roots.test.ts index 0d1ee5301a..a35d81788c 100644 --- a/src/filesystem/__tests__/server-roots.test.ts +++ b/src/filesystem/__tests__/server-roots.test.ts @@ -10,7 +10,7 @@ import fs from "fs/promises"; import path from "path"; import { pathToFileURL } from "url"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import type { Root } from "@modelcontextprotocol/sdk/types.js"; +import type { Root } from "@modelcontextprotocol/server"; import { allowedDirectoriesOf, call, @@ -153,7 +153,7 @@ describe("initial roots", () => { await vi.waitFor(() => expect(stderr).toHaveBeenCalledWith( "Failed to request initial roots from client:", - "MCP error -32603: client has no roots today", + "client has no roots today", ), ); expect(await allowedDirectoriesOf(client)).toEqual([cliDir]); @@ -269,7 +269,7 @@ describe("roots/list_changed", () => { await vi.waitFor(() => expect(stderr).toHaveBeenCalledWith( "Failed to request roots from client:", - "MCP error -32603: gone", + "gone", ), ); expect(await allowedDirectoriesOf(client)).toEqual([rootDir]); diff --git a/src/filesystem/__tests__/server-tools.test.ts b/src/filesystem/__tests__/server-tools.test.ts index ff65d2f53b..9be4855672 100644 --- a/src/filesystem/__tests__/server-tools.test.ts +++ b/src/filesystem/__tests__/server-tools.test.ts @@ -1,7 +1,7 @@ // Characterization suite for every filesystem tool, driven through an SDK // Client over an in-memory transport (#4854). It pins what the server does -// today on SDK 1.x, quirks included, so the SDK v2 migration (#4856) can prove -// it changed nothing on the wire. A test that pins a known bug says so, with +// today, quirks included. It was written on SDK 1.x so the SDK v2 migration +// (#4856) could show what changed on the wire. A test that pins a known bug says so, with // the issue number, so the fix has a test to change. import fs from "fs/promises"; @@ -9,7 +9,7 @@ import path from "path"; import { fileURLToPath } from "url"; import { createRequire } from "node:module"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import type { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import type { Client } from "@modelcontextprotocol/client"; import { allowedDirectoriesOf, call, @@ -149,9 +149,9 @@ describe("server identity and tool list", () => { }); }); - // #4841: the SDK 1.x tools/list handler stamps draft-07 on every schema it - // renders, which validators that accept only JSON Schema 2020-12 reject. The - // server declares 2020-12 instead. + // #4841: the SDK 1.x tools/list handler stamped draft-07 on every schema it + // rendered, which validators that accept only JSON Schema 2020-12 reject. The + // server declares 2020-12 instead, as SDK v2 now does by default. it("declares the 2020-12 $schema on every input and output schema (#4841)", async () => { const { tools } = await client.listTools(); expect(tools).toHaveLength(TOOL_NAMES.length); @@ -307,19 +307,18 @@ describe("server identity and tool list", () => { } }); - it("reports an unknown tool as a tool error", async () => { - const result = await call(client, "no_such_tool"); - expect(result.isError).toBe(true); - expect(textOf(result)).toBe( - "MCP error -32602: Tool no_such_tool not found", - ); + it("rejects an unknown tool with -32602", async () => { + await expect(call(client, "no_such_tool")).rejects.toMatchObject({ + code: -32602, + message: "Tool no_such_tool not found", + }); }); it("reports schema-invalid arguments as a tool error", async () => { const result = await call(client, "read_text_file", { path: 42 }); expect(result.isError).toBe(true); expect(textOf(result)).toMatch( - /^MCP error -32602: Input validation error: Invalid arguments for tool read_text_file/, + /^Input validation error: Invalid arguments for tool read_text_file/, ); }); }); diff --git a/src/filesystem/__tests__/stdio.test.ts b/src/filesystem/__tests__/stdio.test.ts index e55b2595d3..9171adcf20 100644 --- a/src/filesystem/__tests__/stdio.test.ts +++ b/src/filesystem/__tests__/stdio.test.ts @@ -3,7 +3,7 @@ // usage text, the exit on no usable directory) and the real // StdioServerTransport over in-memory streams, writing raw JSON-RPC lines the // way a client process would. Absorbs the former startup-validation.test.ts, -// which spawned the build. Pins #4206, #4207 and #4195 as they stand on SDK 1.x. +// which spawned the build. Pins #4206, #4207 and #4195 as they stand today. import fs from "fs/promises"; import os from "os"; @@ -11,7 +11,7 @@ import path from "path"; import { PassThrough } from "node:stream"; import { pathToFileURL } from "url"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { McpServer } from "@modelcontextprotocol/server"; import { isEntryPoint, main, resolveAllowedDirectories } from "../index.js"; import { makeTempDir, quietStderr } from "./helpers.js"; @@ -203,7 +203,7 @@ describe("startup", () => { }); describe("the stdio transport", () => { - // #4195: SDK 1.x does not enforce the lifecycle, so a request before + // #4195: the SDK does not enforce the lifecycle, so a request before // initialize is answered normally. it("answers tools/list before initialize (#4195)", async () => { await session.start([dir]); diff --git a/src/filesystem/index.ts b/src/filesystem/index.ts index 0a8edced6a..c112d8f23e 100644 --- a/src/filesystem/index.ts +++ b/src/filesystem/index.ts @@ -12,8 +12,8 @@ import { realpathSync } from "fs"; import fs from "fs/promises"; import path from "path"; import { fileURLToPath } from "url"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import type { McpServer } from "@modelcontextprotocol/server"; import { normalizePath, expandHome } from "./path-utils.js"; import { createServer } from "./server.js"; diff --git a/src/filesystem/package.json b/src/filesystem/package.json index 5ee6d9498f..2badd52383 100644 --- a/src/filesystem/package.json +++ b/src/filesystem/package.json @@ -12,6 +12,9 @@ "url": "https://github.com/modelcontextprotocol/servers.git" }, "type": "module", + "engines": { + "node": ">=20" + }, "bin": { "mcp-server-filesystem": "dist/index.js" }, @@ -31,13 +34,14 @@ "validate": "npm run format:check && npm run lint && npm run typecheck && npm run build && npm run test" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.30.0", + "@modelcontextprotocol/server": "^2.3.1", "diff": "^8.0.3", "glob": "^13.0.6", "minimatch": "^10.0.1", - "zod": "^4.0.0" + "zod": "^4.2.0" }, "devDependencies": { + "@modelcontextprotocol/client": "^2.3.1", "@types/diff": "^5.0.9", "@types/minimatch": "^5.1.2", "@types/node": "^22", diff --git a/src/filesystem/roots-utils.ts b/src/filesystem/roots-utils.ts index eda0796620..e70da4fa01 100644 --- a/src/filesystem/roots-utils.ts +++ b/src/filesystem/roots-utils.ts @@ -2,7 +2,7 @@ import { promises as fs, type Stats } from "fs"; import path from "path"; import os from "os"; import { normalizePath } from "./path-utils.js"; -import type { Root } from "@modelcontextprotocol/sdk/types.js"; +import type { Root } from "@modelcontextprotocol/server"; import { fileURLToPath } from "url"; /** diff --git a/src/filesystem/server.ts b/src/filesystem/server.ts index 2a0327d80d..a5aadf2e07 100644 --- a/src/filesystem/server.ts +++ b/src/filesystem/server.ts @@ -4,12 +4,8 @@ // lets the tests drive a real Client against it in-process over an in-memory // transport (#4854), and holding the allow-list in this closure rather than in // a module global lets two instances coexist in one process. - -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { - RootsListChangedNotificationSchema, - type Root, -} from "@modelcontextprotocol/sdk/types.js"; +import { McpServer } from "@modelcontextprotocol/server"; +import type { Root } from "@modelcontextprotocol/server"; import fs from "fs/promises"; import { createReadStream } from "fs"; import path from "path"; @@ -32,10 +28,11 @@ import { import { SERVER_VERSION } from "./version.js"; // The JSON Schema dialect every advertised tool schema declares (#4841). The -// SDK 1.x tools/list handler renders zod schemas with zod's draft-07 target and -// stamps "$schema": draft-07 on each one, which validators that accept only +// SDK 1.x tools/list handler rendered zod schemas with zod's draft-07 target and +// stamped "$schema": draft-07 on each one, which validators that accept only // 2020-12 (the dialect MCP assumes for tool schemas) reject outright. Setting -// $schema in the root object's zod metadata overrides that stamp. The schemas +// $schema in the root object's zod metadata overrode that stamp. SDK v2 stamps +// 2020-12 itself, so the label is now the SDK's default as well. The schemas // below use nothing whose draft-07 and 2020-12 renderings differ (no tuples), // so the label is the only change; server-tools.test.ts fails if a draft-07-only // keyword ever appears under it. @@ -773,7 +770,7 @@ export function createServer( // Handles dynamic roots updates during runtime, when client sends "roots/list_changed" notification, server fetches the updated roots and replaces all allowed directories with the new roots. server.server.setNotificationHandler( - RootsListChangedNotificationSchema, + "notifications/roots/list_changed", async () => { try { // Request the updated roots list from the client diff --git a/src/memory/__tests__/entry-point.test.ts b/src/memory/__tests__/entry-point.test.ts index 63722256c6..5593f0d64b 100644 --- a/src/memory/__tests__/entry-point.test.ts +++ b/src/memory/__tests__/entry-point.test.ts @@ -7,8 +7,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { promises as fs } from "fs"; import path from "path"; import { pathToFileURL } from "url"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; +import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import { isMainModule, main } from "../index.js"; import { call, makeTempGraph, readFileText } from "./helpers.js"; diff --git a/src/memory/__tests__/helpers.ts b/src/memory/__tests__/helpers.ts index a801a015a5..4be893f7c4 100644 --- a/src/memory/__tests__/helpers.ts +++ b/src/memory/__tests__/helpers.ts @@ -6,10 +6,9 @@ import { promises as fs } from "fs"; import os from "os"; import path from "path"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { InMemoryTransport } from "@modelcontextprotocol/server"; +import type { McpServer, CallToolResult } from "@modelcontextprotocol/server"; +import { Client } from "@modelcontextprotocol/client"; import { createServer } from "../index.js"; export interface Connection { diff --git a/src/memory/__tests__/resource.test.ts b/src/memory/__tests__/resource.test.ts index 9f85bd12a7..0e9d119177 100644 --- a/src/memory/__tests__/resource.test.ts +++ b/src/memory/__tests__/resource.test.ts @@ -4,9 +4,7 @@ // that mutation tools send (notifyGraphUpdated). They replace the earlier // tests that called the register functions with a mocked McpServer. import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; -import type { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js"; -import { ResourceUpdatedNotificationSchema } from "@modelcontextprotocol/sdk/types.js"; +import type { Client, JSONRPCMessage } from "@modelcontextprotocol/client"; import { call, connect, makeTempGraph } from "./helpers.js"; import type { Connection } from "./helpers.js"; @@ -31,7 +29,7 @@ describe("knowledge-graph resource over the protocol", () => { client = conn.client; updates = []; client.setNotificationHandler( - ResourceUpdatedNotificationSchema, + "notifications/resources/updated", (notification) => { updates.push(notification.params); }, @@ -102,7 +100,7 @@ describe("knowledge-graph resource over the protocol", () => { it("rejects a URI it does not serve", async () => { await expect( client.readResource({ uri: "memory://other" }), - ).rejects.toThrow("Resource memory://other not found"); + ).rejects.toThrow("Resource not found: memory://other"); }); }); diff --git a/src/memory/__tests__/stdio-smoke.test.ts b/src/memory/__tests__/stdio-smoke.test.ts index ab47e65b40..81cecdf2cf 100644 --- a/src/memory/__tests__/stdio-smoke.test.ts +++ b/src/memory/__tests__/stdio-smoke.test.ts @@ -8,8 +8,8 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { promises as fs } from "fs"; import path from "path"; import { fileURLToPath } from "url"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { StdioClientTransport } from "@modelcontextprotocol/client/stdio"; +import { Client } from "@modelcontextprotocol/client"; import { call, makeTempGraph, readFileText } from "./helpers.js"; const distIndexPath = path.join( diff --git a/src/memory/__tests__/tools.test.ts b/src/memory/__tests__/tools.test.ts index cea031eb93..537fa3b4f8 100644 --- a/src/memory/__tests__/tools.test.ts +++ b/src/memory/__tests__/tools.test.ts @@ -1,14 +1,14 @@ // Characterization tests for the memory server's nine tools, driven through // an SDK Client over an in-memory transport (#4854). They pin what a client -// sees today on SDK 1.x: the advertised tool list, each tool's text and +// sees today: the advertised tool list, each tool's text and // structured results, and the error results. A test that pins a known bug // cites its issue, so the PR that fixes it has a test to change. import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import type { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import type { Client } from "@modelcontextprotocol/client"; import { call, connect, makeTempGraph, textOf } from "./helpers.js"; import type { Connection } from "./helpers.js"; -const DRAFT_07 = "http://json-schema.org/draft-07/schema#"; +const DRAFT_07 = "https://json-schema.org/draft/2020-12/schema"; // JSON Schema fragments as the SDK emits them. Output schemas close every // object with additionalProperties: false; input schemas leave them open. @@ -86,7 +86,10 @@ const readAnnotations = { idempotentHint: true, openWorldHint: false, }; -const execution = { taskSupport: "forbidden" }; +// SDK 1.x advertised `execution: { taskSupport: "forbidden" }` on every tool; +// SDK v2 removed the experimental tasks layer and no longer sends it. Part 5 +// (#4852) decides what a tool advertises under the tasks extension. +const execution = undefined; const expectedTools = [ { @@ -386,19 +389,17 @@ describe("memory tools over the protocol", () => { }); it("offers no prompts", async () => { - await expect(client.listPrompts()).rejects.toThrow(/Method not found/); + // A raw request: SDK v2's listPrompts() answers an empty list without + // asking a server that does not advertise prompts. + await expect( + client.request({ method: "prompts/list", params: {} }), + ).rejects.toThrow(/Method not found/); }); - it("reports an unknown tool as a tool error", async () => { - const result = await call(client, "no_such_tool"); - expect(result).toEqual({ - content: [ - { - type: "text", - text: "MCP error -32602: Tool no_such_tool not found", - }, - ], - isError: true, + it("rejects an unknown tool with -32602", async () => { + await expect(call(client, "no_such_tool")).rejects.toMatchObject({ + code: -32602, + message: "Tool no_such_tool not found", }); }); }); @@ -499,7 +500,7 @@ describe("memory tools over the protocol", () => { content: [ { type: "text", - text: "MCP error -32602: Input validation error: Invalid arguments for tool create_entities: Invalid input: expected array, received undefined at entities", + text: "Input validation error: Invalid arguments for tool create_entities: entities: Invalid input: expected array, received undefined", }, ], isError: true, @@ -873,7 +874,7 @@ describe("memory tools over the protocol", () => { content: [ { type: "text", - text: "MCP error -32602: Input validation error: Invalid arguments for tool search_nodes: Invalid input: expected string, received number at query", + text: "Input validation error: Invalid arguments for tool search_nodes: query: Invalid input: expected string, received number", }, ], isError: true, @@ -888,7 +889,7 @@ describe("memory tools over the protocol", () => { content: [ { type: "text", - text: "MCP error -32602: Input validation error: Invalid arguments for tool search_nodes: Too big: expected string to have <=2048 characters at query", + text: "Input validation error: Invalid arguments for tool search_nodes: query: Too big: expected string to have <=2048 characters", }, ], isError: true, diff --git a/src/memory/index.ts b/src/memory/index.ts index eab385a9fa..0e6bd9ffdc 100644 --- a/src/memory/index.ts +++ b/src/memory/index.ts @@ -5,14 +5,9 @@ // subscribable resource. createServer() builds a server over a graph file so // tests can run it in-process; main() connects it to stdio, and runs only // when this file is the process entry point, so importing it starts nothing. - -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; -import { - SubscribeRequestSchema, - UnsubscribeRequestSchema, -} from "@modelcontextprotocol/sdk/types.js"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { McpServer } from "@modelcontextprotocol/server"; +import type { Transport } from "@modelcontextprotocol/server"; import { z } from "zod"; import { promises as fs, realpathSync } from "fs"; import type { FileHandle } from "fs/promises"; @@ -773,14 +768,14 @@ export function createServer(memoryFilePath: string): McpServer { title: "Create Entities", description: "Create multiple new entities in the knowledge graph. An entity whose name already exists, or repeats an earlier entity in the same call, is skipped and its observations are not added; the result lists the skipped names in `skipped`. Use add_observations to add observations to an existing entity.", - inputSchema: { + inputSchema: z.object({ entities: z.array(EntitySchema), - }, - outputSchema: { + }), + outputSchema: z.object({ entities: z.array(EntitySchema), // Present only when at least one requested entity was skipped. skipped: z.array(z.string()).optional(), - }, + }), annotations: { readOnlyHint: false, destructiveHint: false, @@ -820,12 +815,12 @@ export function createServer(memoryFilePath: string): McpServer { title: "Create Relations", description: "Create multiple new relations between entities in the knowledge graph. Relations should be in active voice", - inputSchema: { + inputSchema: z.object({ relations: z.array(RelationSchema), - }, - outputSchema: { + }), + outputSchema: z.object({ relations: z.array(RelationSchema), - }, + }), annotations: { readOnlyHint: false, destructiveHint: false, @@ -852,7 +847,7 @@ export function createServer(memoryFilePath: string): McpServer { title: "Add Observations", description: "Add new observations to existing entities in the knowledge graph", - inputSchema: { + inputSchema: z.object({ observations: z.array( z.object({ entityName: z @@ -863,15 +858,15 @@ export function createServer(memoryFilePath: string): McpServer { .describe("An array of observation contents to add"), }), ), - }, - outputSchema: { + }), + outputSchema: z.object({ results: z.array( z.object({ entityName: z.string(), addedObservations: z.array(z.string()), }), ), - }, + }), annotations: { readOnlyHint: false, destructiveHint: false, @@ -898,15 +893,15 @@ export function createServer(memoryFilePath: string): McpServer { title: "Delete Entities", description: "Delete multiple entities and their associated relations from the knowledge graph", - inputSchema: { + inputSchema: z.object({ entityNames: z .array(z.string()) .describe("An array of entity names to delete"), - }, - outputSchema: { + }), + outputSchema: z.object({ success: z.boolean(), message: z.string(), - }, + }), annotations: { readOnlyHint: false, destructiveHint: true, @@ -936,7 +931,7 @@ export function createServer(memoryFilePath: string): McpServer { title: "Delete Observations", description: "Delete specific observations from entities in the knowledge graph", - inputSchema: { + inputSchema: z.object({ deletions: z.array( z.object({ entityName: z @@ -947,11 +942,11 @@ export function createServer(memoryFilePath: string): McpServer { .describe("An array of observations to delete"), }), ), - }, - outputSchema: { + }), + outputSchema: z.object({ success: z.boolean(), message: z.string(), - }, + }), annotations: { readOnlyHint: false, destructiveHint: true, @@ -987,15 +982,15 @@ export function createServer(memoryFilePath: string): McpServer { { title: "Delete Relations", description: "Delete multiple relations from the knowledge graph", - inputSchema: { + inputSchema: z.object({ relations: z .array(RelationSchema) .describe("An array of relations to delete"), - }, - outputSchema: { + }), + outputSchema: z.object({ success: z.boolean(), message: z.string(), - }, + }), annotations: { readOnlyHint: false, destructiveHint: true, @@ -1024,11 +1019,11 @@ export function createServer(memoryFilePath: string): McpServer { { title: "Read Graph", description: "Read the entire knowledge graph", - inputSchema: {}, - outputSchema: { + inputSchema: z.object({}), + outputSchema: z.object({ entities: z.array(EntitySchema), relations: z.array(RelationSchema), - }, + }), annotations: { readOnlyHint: true, destructiveHint: false, @@ -1053,13 +1048,13 @@ export function createServer(memoryFilePath: string): McpServer { { title: "Search Nodes", description: "Search for nodes in the knowledge graph based on a query", - inputSchema: { + inputSchema: z.object({ query: SearchNodesQuerySchema, - }, - outputSchema: { + }), + outputSchema: z.object({ entities: z.array(EntitySchema), relations: z.array(RelationSchema), - }, + }), annotations: { readOnlyHint: true, destructiveHint: false, @@ -1084,15 +1079,15 @@ export function createServer(memoryFilePath: string): McpServer { { title: "Open Nodes", description: "Open specific nodes in the knowledge graph by their names", - inputSchema: { + inputSchema: z.object({ names: z .array(z.string()) .describe("An array of entity names to retrieve"), - }, - outputSchema: { + }), + outputSchema: z.object({ entities: z.array(EntitySchema), relations: z.array(RelationSchema), - }, + }), annotations: { readOnlyHint: true, destructiveHint: false, @@ -1151,11 +1146,11 @@ export function registerKnowledgeGraphSubscriptions( resourceSubscribers: Set, ) { server.server.registerCapabilities({ resources: { subscribe: true } }); - server.server.setRequestHandler(SubscribeRequestSchema, async (request) => { + server.server.setRequestHandler("resources/subscribe", async (request) => { resourceSubscribers.add(request.params.uri); return {}; }); - server.server.setRequestHandler(UnsubscribeRequestSchema, async (request) => { + server.server.setRequestHandler("resources/unsubscribe", async (request) => { resourceSubscribers.delete(request.params.uri); return {}; }); diff --git a/src/memory/package.json b/src/memory/package.json index 3ce170f295..3d5fb93444 100644 --- a/src/memory/package.json +++ b/src/memory/package.json @@ -12,6 +12,9 @@ "url": "https://github.com/modelcontextprotocol/servers.git" }, "type": "module", + "engines": { + "node": ">=20" + }, "bin": { "mcp-server-memory": "dist/index.js" }, @@ -31,10 +34,11 @@ "validate": "npm run format:check && npm run lint && npm run typecheck && npm run build && npm run test" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.30.0", - "zod": "^4.0.0" + "@modelcontextprotocol/server": "^2.3.1", + "zod": "^4.2.0" }, "devDependencies": { + "@modelcontextprotocol/client": "^2.3.1", "@types/node": "^22", "@vitest/coverage-v8": "^4.1.8", "shx": "^0.4.0", diff --git a/src/sequentialthinking/__tests__/entry-point.test.ts b/src/sequentialthinking/__tests__/entry-point.test.ts index 1b763a02d1..4ac351e738 100644 --- a/src/sequentialthinking/__tests__/entry-point.test.ts +++ b/src/sequentialthinking/__tests__/entry-point.test.ts @@ -17,8 +17,7 @@ import { mkdtempSync, rmSync, symlinkSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; +import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import { isEntryPoint, main } from "../index.js"; const indexUrl = new URL("../index.ts", import.meta.url).href; diff --git a/src/sequentialthinking/__tests__/helpers.ts b/src/sequentialthinking/__tests__/helpers.ts index 1d0d8940f3..d36f8e45e7 100644 --- a/src/sequentialthinking/__tests__/helpers.ts +++ b/src/sequentialthinking/__tests__/helpers.ts @@ -7,8 +7,7 @@ // and type changes alone. import { expect } from "vitest"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; +import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import { createServer } from "../index.js"; export const TOOL_NAME = "sequentialthinking"; diff --git a/src/sequentialthinking/__tests__/input-schema.test.ts b/src/sequentialthinking/__tests__/input-schema.test.ts index f5ff488b90..8df78f6a4e 100644 --- a/src/sequentialthinking/__tests__/input-schema.test.ts +++ b/src/sequentialthinking/__tests__/input-schema.test.ts @@ -3,7 +3,7 @@ // error a client gets back for each kind of bad input. Originally the #4651 // regression test, which spawned dist/index.js and was skipped when no build // existed; ported to the in-process client (#4854) so it always runs and -// always tests the source. The error texts are the SDK 1.x mapping of a Zod +// always tests the source. The error texts are the SDK's mapping of a Zod // failure and are pinned verbatim: a change to them is a wire change. import { describe, it, expect, beforeEach, afterEach } from "vitest"; @@ -16,7 +16,7 @@ import { } from "./helpers.js"; const INVALID = - "MCP error -32602: Input validation error: Invalid arguments for tool sequentialthinking: "; + "Input validation error: Invalid arguments for tool sequentialthinking: "; describe("sequentialthinking input schema", () => { let conn: Connected; @@ -43,7 +43,7 @@ describe("sequentialthinking input schema", () => { it("rejects a call that omits nextThoughtNeeded", async () => { await expectInvalid( { thought: "t", thoughtNumber: 1, totalThoughts: 1 }, - "Invalid input at nextThoughtNeeded", + "nextThoughtNeeded: Invalid input", ); }); @@ -51,16 +51,16 @@ describe("sequentialthinking input schema", () => { const { thought: _omitted, ...args } = thought(); await expectInvalid( args, - "Invalid input: expected string, received undefined at thought", + "thought: Invalid input: expected string, received undefined", ); }); it("reports every missing numeric field, coerced to NaN", async () => { await expectInvalid( { thought: "t" }, - "Invalid input at nextThoughtNeeded\n" + - "Invalid input: expected number, received NaN at thoughtNumber\n" + - "Invalid input: expected number, received NaN at totalThoughts", + "nextThoughtNeeded: Invalid input, " + + "thoughtNumber: Invalid input: expected number, received NaN, " + + "totalThoughts: Invalid input: expected number, received NaN", ); }); @@ -95,7 +95,7 @@ describe("sequentialthinking input schema", () => { async (value) => { await expectInvalid( thought({ nextThoughtNeeded: value }), - `Expected boolean or "true"/"false" string, received "${value}" at nextThoughtNeeded`, + `nextThoughtNeeded: Expected boolean or "true"/"false" string, received "${value}"`, ); }, ); @@ -103,7 +103,7 @@ describe("sequentialthinking input schema", () => { it.each([1, 0, null])("rejects the non-string %j", async (value) => { await expectInvalid( thought({ nextThoughtNeeded: value }), - "Invalid input at nextThoughtNeeded", + "nextThoughtNeeded: Invalid input", ); }); @@ -117,11 +117,11 @@ describe("sequentialthinking input schema", () => { ).toBeFalsy(); await expectInvalid( thought({ isRevision: "maybe" }), - 'Expected boolean or "true"/"false" string, received "maybe" at isRevision', + 'isRevision: Expected boolean or "true"/"false" string, received "maybe"', ); await expectInvalid( thought({ needsMoreThoughts: "nope" }), - 'Expected boolean or "true"/"false" string, received "nope" at needsMoreThoughts', + 'needsMoreThoughts: Expected boolean or "true"/"false" string, received "nope"', ); }); }); @@ -156,13 +156,13 @@ describe("sequentialthinking input schema", () => { "Invalid input: expected number, received NaN", ], ])("rejects %s = %j", async (field, value, detail) => { - await expectInvalid(thought({ [field]: value }), `${detail} at ${field}`); + await expectInvalid(thought({ [field]: value }), `${field}: ${detail}`); }); it("rejects a non-string branchId", async () => { await expectInvalid( thought({ branchFromThought: 1, branchId: 7 }), - "Invalid input: expected string, received number at branchId", + "branchId: Invalid input: expected string, received number", ); }); }); diff --git a/src/sequentialthinking/__tests__/stdio-smoke.test.ts b/src/sequentialthinking/__tests__/stdio-smoke.test.ts index 9c44f30335..f7924198c1 100644 --- a/src/sequentialthinking/__tests__/stdio-smoke.test.ts +++ b/src/sequentialthinking/__tests__/stdio-smoke.test.ts @@ -10,8 +10,8 @@ import { existsSync, mkdtempSync, rmSync, symlinkSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { StdioClientTransport } from "@modelcontextprotocol/client/stdio"; +import { Client } from "@modelcontextprotocol/client"; const packageJson = createRequire(import.meta.url)("../package.json") as { version: string; diff --git a/src/sequentialthinking/__tests__/tools-list.test.ts b/src/sequentialthinking/__tests__/tools-list.test.ts index d1a9380dba..69ef0455a4 100644 --- a/src/sequentialthinking/__tests__/tools-list.test.ts +++ b/src/sequentialthinking/__tests__/tools-list.test.ts @@ -1,6 +1,6 @@ // Pins what a client sees when it connects and lists tools: the server's // identity and capabilities, and the one tool's name, title, description, -// annotations, input schema and output schema, exactly as SDK 1.x emits them +// annotations, input schema and output schema, exactly as the SDK emits them // today. These are characterization tests. The annotations assert the values // fixed in #4721 (the tool is stateful, so neither read-only nor idempotent). // The 2781-character description (#799) is pinned as current design: the @@ -64,7 +64,9 @@ describe("sequentialthinking: initialize and tools/list", () => { }); }); - it("forbids task-augmented execution", () => { + // SDK v2 removed the experimental tasks layer and no longer advertises + // `execution.taskSupport`; Part 5 (#4852) restores task semantics. + it.skip("forbids task-augmented execution", () => { expect(tool.execution).toEqual({ taskSupport: "forbidden" }); }); @@ -80,9 +82,9 @@ describe("sequentialthinking: initialize and tools/list", () => { ); }); - it("advertises the input schema as draft-07 JSON Schema", () => { + it("advertises the input schema as JSON Schema 2020-12", () => { expect(tool.inputSchema).toEqual({ - $schema: "http://json-schema.org/draft-07/schema#", + $schema: "https://json-schema.org/draft/2020-12/schema", type: "object", properties: { thought: { type: "string", description: "Your current thinking step" }, @@ -136,7 +138,7 @@ describe("sequentialthinking: initialize and tools/list", () => { it("advertises a closed output schema with all five fields required", () => { expect(tool.outputSchema).toEqual({ - $schema: "http://json-schema.org/draft-07/schema#", + $schema: "https://json-schema.org/draft/2020-12/schema", type: "object", properties: { thoughtNumber: { type: "number" }, @@ -161,25 +163,22 @@ describe("sequentialthinking: initialize and tools/list", () => { }); it("offers no prompts or resources", async () => { - await expect(conn.client.listPrompts()).rejects.toThrow(/Method not found/); - await expect(conn.client.listResources()).rejects.toThrow( - /Method not found/, - ); + // Raw requests: SDK v2's listPrompts() and listResources() answer empty + // lists without asking a server that does not advertise them. + await expect( + conn.client.request({ method: "prompts/list", params: {} }), + ).rejects.toThrow(/Method not found/); + await expect( + conn.client.request({ method: "resources/list", params: {} }), + ).rejects.toThrow(/Method not found/); }); - it("reports an unknown tool as a tool error", async () => { - const result = await conn.client.callTool({ - name: "no_such_tool", - arguments: {}, - }); - expect(result).toEqual({ - content: [ - { - type: "text", - text: "MCP error -32602: Tool no_such_tool not found", - }, - ], - isError: true, + it("rejects an unknown tool with -32602", async () => { + await expect( + conn.client.callTool({ name: "no_such_tool", arguments: {} }), + ).rejects.toMatchObject({ + code: -32602, + message: "Tool no_such_tool not found", }); }); }); diff --git a/src/sequentialthinking/index.ts b/src/sequentialthinking/index.ts index 5bc02e4602..a03d27f469 100644 --- a/src/sequentialthinking/index.ts +++ b/src/sequentialthinking/index.ts @@ -8,9 +8,9 @@ import { realpathSync } from "node:fs"; import { fileURLToPath } from "node:url"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { McpServer } from "@modelcontextprotocol/server"; +import type { Transport } from "@modelcontextprotocol/server"; import { z } from "zod"; import { SequentialThinkingServer } from "./lib.js"; import { SERVER_VERSION } from "./version.js"; @@ -100,7 +100,7 @@ You should: 9. Repeat the process until satisfied with the solution 10. Provide a single, ideally correct answer as the final output 11. Only set nextThoughtNeeded to false when truly done and a satisfactory answer is reached`, - inputSchema: { + inputSchema: z.object({ thought: z.string().describe("Your current thinking step"), nextThoughtNeeded: coercedBoolean.describe( "Whether another thought step is needed", @@ -136,7 +136,7 @@ You should: needsMoreThoughts: coercedBoolean .optional() .describe("If more thoughts are needed"), - }, + }), annotations: { // Every call appends to the server's in-memory thought history, and a // call with both branchFromThought and branchId also appends to that @@ -146,13 +146,13 @@ You should: idempotentHint: false, openWorldHint: false, }, - outputSchema: { + outputSchema: z.object({ thoughtNumber: z.number(), totalThoughts: z.number(), nextThoughtNeeded: z.boolean(), branches: z.array(z.string()), thoughtHistoryLength: z.number(), - }, + }), }, async (args) => { const result = thinkingServer.processThought(args); diff --git a/src/sequentialthinking/package.json b/src/sequentialthinking/package.json index 91814c7de7..42fa282ed9 100644 --- a/src/sequentialthinking/package.json +++ b/src/sequentialthinking/package.json @@ -12,6 +12,9 @@ "url": "https://github.com/modelcontextprotocol/servers.git" }, "type": "module", + "engines": { + "node": ">=20" + }, "bin": { "mcp-server-sequential-thinking": "dist/index.js" }, @@ -31,12 +34,13 @@ "validate": "npm run format:check && npm run lint && npm run typecheck && npm run build && npm run test" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.30.0", + "@modelcontextprotocol/server": "^2.3.1", "chalk": "^5.3.0", "yargs": "^17.7.2", - "zod": "^4.0.0" + "zod": "^4.2.0" }, "devDependencies": { + "@modelcontextprotocol/client": "^2.3.1", "@types/node": "^22", "@types/yargs": "^17.0.32", "@vitest/coverage-v8": "^4.1.8", From ccab68c44a7c29d176c9d52752dc4531a99edba5 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 12:28:29 -0400 Subject: [PATCH 03/65] docs(skills): the TypeScript servers are on SDK v2 but still legacy-era only (#4856) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .claude/skills/client-smoke/SKILL.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.claude/skills/client-smoke/SKILL.md b/.claude/skills/client-smoke/SKILL.md index df76bc1b1d..a650902175 100644 --- a/.claude/skills/client-smoke/SKILL.md +++ b/.claude/skills/client-smoke/SKILL.md @@ -172,9 +172,10 @@ CLI) or **Server Settings → Protocol Era** (the web UI): | `modern` | Pins 2026-07-28 through `server/discover`, with no fallback | | `auto` | Probes `server/discover`, and falls back to `initialize` | -⚠️ **Only the legacy era can be exercised against these servers today.** All -seven are on a v1 SDK (`@modelcontextprotocol/sdk` 1.x, `mcp` 1.x), whose -newest protocol version is 2025-11-25. None implements `server/discover`. So: +⚠️ **Only the legacy era can be exercised against these servers today.** The +Python servers are on `mcp` 1.x, whose newest protocol version is 2025-11-25. +The TypeScript servers are on the TS SDK v2 packages (#4856) but still serve +only the `initialize` handshake. None implements `server/discover`. So: - `--protocol-era modern` fails, by design, with `Version negotiation failed: the server did not offer pinned protocol version 2026-07-28 via @@ -259,7 +260,7 @@ Claude Code, run this way, has no era switch and does not print the protocol version it negotiated. `/pr-flow` asks for the LLM client in both eras as well; where the client gives no way to choose, record the client and its version and say that the era could not be selected, rather than labelling the run with an -era it was not shown to use. Against a v1-SDK server the connection is legacy +era it was not shown to use. Against a server without modern-era support the connection is legacy whatever the client prefers, for the reason given under Protocol eras. ## What to write down From d13408dcdc0845d4ce1d323c7a05adf309852e31 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 12:34:45 -0400 Subject: [PATCH 04/65] Serve only the legacy era from the Python servers SDK v2's Server.run() drives a dual-era loop, so the port also answered server/discover and 2026-07-28 per-request envelopes. That changes the wire, and adopting 2026-07-28 is #4853, not #4851. Each server now runs the SDK's handshake-only serve_loop (what run() uses minus era routing) inside the server lifespan, so an enveloped request gets "Method not found" as it did on SDK v1. Each suite gains a protocol test pinning that refusal; git's raise_exceptions test (#4658) now patches serve_loop instead of Server.run. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/fetch/src/mcp_server_fetch/server.py | 14 +++++- src/fetch/tests/test_protocol.py | 61 ++++++++++++++++++++++- src/git/src/mcp_server_git/server.py | 14 +++++- src/git/tests/test_protocol.py | 62 +++++++++++++++++++++++- src/git/tests/test_server.py | 8 ++- src/time/src/mcp_server_time/server.py | 14 +++++- src/time/tests/test_protocol.py | 54 +++++++++++++++++++++ 7 files changed, 220 insertions(+), 7 deletions(-) diff --git a/src/fetch/src/mcp_server_fetch/server.py b/src/fetch/src/mcp_server_fetch/server.py index 2adb74dcaf..ede3bd31b0 100644 --- a/src/fetch/src/mcp_server_fetch/server.py +++ b/src/fetch/src/mcp_server_fetch/server.py @@ -12,6 +12,7 @@ import readabilipy.simple_json from mcp.shared.exceptions import MCPError from mcp.server import Server, ServerRequestContext +from mcp.server.runner import serve_loop from mcp.server.stdio import stdio_server from mcp.types import ( CallToolRequestParams, @@ -536,4 +537,15 @@ async def get_prompt( options = server.create_initialization_options() async with stdio_server() as (read_stream, write_stream): - await server.run(read_stream, write_stream, options, raise_exceptions=False) + # Legacy era only. Server.run() would also serve 2026-07-28 (its + # dual-era loop answers server/discover and per-request envelopes); + # adopting that era is #4853, so this port (#4851) serves the + # handshake loop alone and keeps the wire unchanged. + async with server.lifespan(server) as lifespan_state: + await serve_loop( + server, + read_stream, + write_stream, + lifespan_state=lifespan_state, + init_options=options, + ) diff --git a/src/fetch/tests/test_protocol.py b/src/fetch/tests/test_protocol.py index b24f739025..935e3b018e 100644 --- a/src/fetch/tests/test_protocol.py +++ b/src/fetch/tests/test_protocol.py @@ -16,18 +16,25 @@ from __future__ import annotations import os +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager from importlib.metadata import version from typing import Any +from unittest.mock import patch +import anyio import httpx import pytest from mcp import ClientSession from mcp.shared.exceptions import MCPError -from mcp.types import INVALID_PARAMS, CallToolResult +from mcp.shared.memory import create_client_server_memory_streams +from mcp.shared.message import SessionMessage +from mcp.types import INVALID_PARAMS, METHOD_NOT_FOUND, CallToolResult, JSONRPCRequest from mcp_server_fetch.server import ( DEFAULT_USER_AGENT_AUTONOMOUS, DEFAULT_USER_AGENT_MANUAL, + serve, ) from .harness import FakeWeb, connect @@ -998,3 +1005,55 @@ async def test_get_prompt_does_not_validate_the_url( assert data["messages"][0]["content"]["text"].startswith( "Failed to fetch not a url: UnsupportedProtocol(" ) + + +# --------------------------------------------------------------------------- +# Protocol era +# --------------------------------------------------------------------------- + + +async def test_modern_era_is_not_served() -> None: + # #4851: the SDK v2 port serves the 2025-11-25 handshake era only. + # Server.run() would also open a 2026-07-28 connection for a request that + # carries the per-request `_meta` envelope; adopting that era is #4853. + # Until then such a request is refused as it was on SDK v1. + discover = JSONRPCRequest( + jsonrpc="2.0", + id=1, + method="server/discover", + params={ + "_meta": { + "io.modelcontextprotocol/protocolVersion": "2026-07-28", + "io.modelcontextprotocol/clientInfo": {"name": "test", "version": "0"}, + "io.modelcontextprotocol/clientCapabilities": {}, + } + }, + ) + response: SessionMessage | Exception | None = None + async with create_client_server_memory_streams() as ( + (client_read, client_write), + server_streams, + ): + + @asynccontextmanager + async def fake_stdio_server() -> AsyncIterator[Any]: + yield server_streams + + with patch("mcp_server_fetch.server.stdio_server", fake_stdio_server): + async with anyio.create_task_group() as tg: + tg.start_soon(serve) + await client_write.send(SessionMessage(discover)) + with anyio.fail_after(5): + response = await client_read.receive() + tg.cancel_scope.cancel() + + assert isinstance(response, SessionMessage) + assert response.message.model_dump(by_alias=True, exclude_none=True) == { + "jsonrpc": "2.0", + "id": 1, + "error": { + "code": METHOD_NOT_FOUND, + "message": "Method not found", + "data": "server/discover", + }, + } diff --git a/src/git/src/mcp_server_git/server.py b/src/git/src/mcp_server_git/server.py index a43ad717e7..34716ec9b8 100644 --- a/src/git/src/mcp_server_git/server.py +++ b/src/git/src/mcp_server_git/server.py @@ -4,6 +4,7 @@ from typing import Any, Optional import jsonschema from mcp.server import Server, ServerRequestContext +from mcp.server.runner import serve_loop from mcp.server.stdio import stdio_server from mcp.types import ( CallToolRequestParams, @@ -760,4 +761,15 @@ async def call_tool( ) options = server.create_initialization_options() async with stdio_server() as (read_stream, write_stream): - await server.run(read_stream, write_stream, options) + # Legacy era only. Server.run() would also serve 2026-07-28 (its + # dual-era loop answers server/discover and per-request envelopes); + # adopting that era is #4853, so this port (#4851) serves the + # handshake loop alone and keeps the wire unchanged. + async with server.lifespan(server) as lifespan_state: + await serve_loop( + server, + read_stream, + write_stream, + lifespan_state=lifespan_state, + init_options=options, + ) diff --git a/src/git/tests/test_protocol.py b/src/git/tests/test_protocol.py index 39c0bc749f..77a054b2a9 100644 --- a/src/git/tests/test_protocol.py +++ b/src/git/tests/test_protocol.py @@ -10,17 +10,25 @@ import json import re +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager from importlib.metadata import version from pathlib import Path from typing import Any from unittest import mock +from unittest.mock import patch +import anyio import git import pytest from mcp.client.session import ClientRequestContext -from mcp.types import ListRootsResult, Root +from mcp.shared.memory import create_client_server_memory_streams +from mcp.shared.message import SessionMessage +from mcp.types import METHOD_NOT_FOUND, JSONRPCRequest, ListRootsResult, Root from pydantic import FileUrl +from mcp_server_git.server import serve + from conftest import connect, make_repo, text_result, wire SNAPSHOTS = Path(__file__).parent / "snapshots" @@ -1146,3 +1154,55 @@ async def test_server_without_client_roots_behaves_the_same(repo: git.Repo): await session.call_tool("git_status", {"repo_path": str(root_of(repo))}) ) assert result["isError"] is False + + +# --------------------------------------------------------------------------- +# Protocol era +# --------------------------------------------------------------------------- + + +async def test_modern_era_is_not_served() -> None: + # #4851: the SDK v2 port serves the 2025-11-25 handshake era only. + # Server.run() would also open a 2026-07-28 connection for a request that + # carries the per-request `_meta` envelope; adopting that era is #4853. + # Until then such a request is refused as it was on SDK v1. + discover = JSONRPCRequest( + jsonrpc="2.0", + id=1, + method="server/discover", + params={ + "_meta": { + "io.modelcontextprotocol/protocolVersion": "2026-07-28", + "io.modelcontextprotocol/clientInfo": {"name": "test", "version": "0"}, + "io.modelcontextprotocol/clientCapabilities": {}, + } + }, + ) + response: SessionMessage | Exception | None = None + async with create_client_server_memory_streams() as ( + (client_read, client_write), + server_streams, + ): + + @asynccontextmanager + async def fake_stdio_server() -> AsyncIterator[Any]: + yield server_streams + + with patch("mcp_server_git.server.stdio_server", fake_stdio_server): + async with anyio.create_task_group() as tg: + tg.start_soon(serve, None) + await client_write.send(SessionMessage(discover)) + with anyio.fail_after(5): + response = await client_read.receive() + tg.cancel_scope.cancel() + + assert isinstance(response, SessionMessage) + assert response.message.model_dump(by_alias=True, exclude_none=True) == { + "jsonrpc": "2.0", + "id": 1, + "error": { + "code": METHOD_NOT_FOUND, + "message": "Method not found", + "data": "server/discover", + }, + } diff --git a/src/git/tests/test_server.py b/src/git/tests/test_server.py index b827ee001a..9270d52ae0 100644 --- a/src/git/tests/test_server.py +++ b/src/git/tests/test_server.py @@ -853,7 +853,11 @@ def test_git_log_date_filtering(test_repository): async def test_serve_run_does_not_raise_exceptions(tmp_path: Path): - """Verify that serve() runs server.run without raise_exceptions=True.""" + """Verify that serve() runs the server loop without raise_exceptions=True. + + serve() drives SDK v2's legacy-only serve_loop rather than Server.run + (#4851), so that is what is patched. + """ repo_path = tmp_path / "serve_test_repo" git.Repo.init(repo_path).close() @@ -864,7 +868,7 @@ async def test_serve_run_does_not_raise_exceptions(tmp_path: Path): mock_stdio.return_value.__aexit__.return_value = None with mock.patch( - "mcp_server_git.server.Server.run", new_callable=mock.AsyncMock + "mcp_server_git.server.serve_loop", new_callable=mock.AsyncMock ) as mock_run: await serve(repo_path) mock_run.assert_awaited_once() diff --git a/src/time/src/mcp_server_time/server.py b/src/time/src/mcp_server_time/server.py index 57fb2763df..3bbc8690b5 100644 --- a/src/time/src/mcp_server_time/server.py +++ b/src/time/src/mcp_server_time/server.py @@ -10,6 +10,7 @@ import jsonschema from mcp.server import Server, ServerRequestContext +from mcp.server.runner import serve_loop from mcp.server.stdio import stdio_server from mcp.types import ( CallToolRequestParams, @@ -292,4 +293,15 @@ async def call_tool( ) options = server.create_initialization_options() async with stdio_server() as (read_stream, write_stream): - await server.run(read_stream, write_stream, options) + # Legacy era only. Server.run() would also serve 2026-07-28 (its + # dual-era loop answers server/discover and per-request envelopes); + # adopting that era is #4853, so this port (#4851) serves the + # handshake loop alone and keeps the wire unchanged. + async with server.lifespan(server) as lifespan_state: + await serve_loop( + server, + read_stream, + write_stream, + lifespan_state=lifespan_state, + init_options=options, + ) diff --git a/src/time/tests/test_protocol.py b/src/time/tests/test_protocol.py index e3f99f6d9e..de5b7aa649 100644 --- a/src/time/tests/test_protocol.py +++ b/src/time/tests/test_protocol.py @@ -33,6 +33,8 @@ import pytest from mcp import ClientSession from mcp.shared.memory import create_client_server_memory_streams +from mcp.shared.message import SessionMessage +from mcp.types import METHOD_NOT_FOUND, JSONRPCRequest from pydantic import BaseModel from mcp_server_time.server import serve @@ -920,3 +922,55 @@ async def test_session_survives_errors() -> None: await call(session, "get_current_time", {"timezone": "Bad/Zone"}) result = await call(session, "get_current_time", {"timezone": "UTC"}) assert result["isError"] is False + + +# --------------------------------------------------------------------------- +# Protocol era +# --------------------------------------------------------------------------- + + +async def test_modern_era_is_not_served() -> None: + # #4851: the SDK v2 port serves the 2025-11-25 handshake era only. + # Server.run() would also open a 2026-07-28 connection for a request that + # carries the per-request `_meta` envelope; adopting that era is #4853. + # Until then such a request is refused as it was on SDK v1. + discover = JSONRPCRequest( + jsonrpc="2.0", + id=1, + method="server/discover", + params={ + "_meta": { + "io.modelcontextprotocol/protocolVersion": "2026-07-28", + "io.modelcontextprotocol/clientInfo": {"name": "test", "version": "0"}, + "io.modelcontextprotocol/clientCapabilities": {}, + } + }, + ) + response: SessionMessage | Exception | None = None + async with create_client_server_memory_streams() as ( + (client_read, client_write), + server_streams, + ): + + @asynccontextmanager + async def fake_stdio_server() -> AsyncIterator[Any]: + yield server_streams + + with patch("mcp_server_time.server.stdio_server", fake_stdio_server): + async with anyio.create_task_group() as tg: + tg.start_soon(serve, "UTC") + await client_write.send(SessionMessage(discover)) + with anyio.fail_after(5): + response = await client_read.receive() + tg.cancel_scope.cancel() + + assert isinstance(response, SessionMessage) + assert response.message.model_dump(by_alias=True, exclude_none=True) == { + "jsonrpc": "2.0", + "id": 1, + "error": { + "code": METHOD_NOT_FOUND, + "message": "Method not found", + "data": "server/discover", + }, + } From b614ecc260b9e1d3f0f592a947919bb163034285 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 12:36:22 -0400 Subject: [PATCH 05/65] fix: address Copilot round 1 on #5063 Drop the stale taskStore references from the testing skill, fix a stray asterisk in tasks.test.ts's header, and rename memory's DRAFT_07 schema constant to JSON_SCHEMA_2020_12 now that it holds the 2020-12 URI. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .claude/skills/testing/SKILL.md | 6 ++--- src/everything/__tests__/tasks.test.ts | 3 ++- src/memory/__tests__/tools.test.ts | 34 ++++++++++++++------------ 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/.claude/skills/testing/SKILL.md b/.claude/skills/testing/SKILL.md index bfaecb72d8..1527dbce82 100644 --- a/.claude/skills/testing/SKILL.md +++ b/.claude/skills/testing/SKILL.md @@ -72,7 +72,7 @@ per-session setup and teardown the server needs. | Server | Factory | Harness | `connect(…)` takes | | --- | --- | --- | --- | -| `everything` | `createServer()` in `server/index.ts`, returns `{ server, cleanup }` | `__tests__/harness.ts` | `{ capabilities, taskStore, setup, sessionId }`, all optional | +| `everything` | `createServer()` in `server/index.ts`, returns `{ server, cleanup }` | `__tests__/harness.ts` | `{ capabilities, setup, sessionId }`, all optional | | `filesystem` | `createServer(allowedDirectories)` in `server.ts` | `__tests__/helpers.ts` | the allowed directories, then `{ capabilities, listRoots }` | | `memory` | `createServer(memoryFilePath)` in `index.ts` | `__tests__/helpers.ts` | the graph file's path (`makeTempGraph()` makes one) | | `sequentialthinking` | `createServer()` in `index.ts` | `__tests__/helpers.ts` | `{ disableThoughtLogging }`, defaulting to `"true"` | @@ -123,12 +123,12 @@ What `everything`'s harness does that a hand-rolled connection would miss: next test. `connect()` sets a fresh `sessionId` on the server transport (`sessionId: null` keeps it undefined, as stdio does), and `close()` runs the server's `cleanup(sessionId)`, which also stops the timers `createServer()` - starts (the roots sync, the task store). + starts (the roots sync). - **Capabilities.** A tool `everything` registers in `registerConditionalTools` appears only when the client declares the matching capability: pass `capabilities` (`ALL_CAPABILITIES` declares every one), with `setup` to install the client's handlers for the requests the server sends back - (sampling, elicitation, roots) and `taskStore` for the task-augmented ones. + (sampling, elicitation, roots). - **Notifications.** `session.notifications` collects every notification no specific handler consumed; `ofMethod(notifications, method)` filters it. diff --git a/src/everything/__tests__/tasks.test.ts b/src/everything/__tests__/tasks.test.ts index 1d8318b395..d90c266f3f 100644 --- a/src/everything/__tests__/tasks.test.ts +++ b/src/everything/__tests__/tasks.test.ts @@ -11,7 +11,8 @@ * capabilities and keeps its tasks in an `InMemoryTaskStore`. * * Every wait in these tools is a `setTimeout` (one-second stages and polls), - * so the tests fake `setTimeout` and advance the clock. * + * so the tests fake `setTimeout` and advance the clock. + * * SDK v2 removed the experimental tasks layer these tools and tests were * built on (SEP-2663), so the three tools are gone and every test here is * skipped until Part 5 (#4852) re-implements them on the diff --git a/src/memory/__tests__/tools.test.ts b/src/memory/__tests__/tools.test.ts index 537fa3b4f8..170d5933e5 100644 --- a/src/memory/__tests__/tools.test.ts +++ b/src/memory/__tests__/tools.test.ts @@ -8,7 +8,7 @@ import type { Client } from "@modelcontextprotocol/client"; import { call, connect, makeTempGraph, textOf } from "./helpers.js"; import type { Connection } from "./helpers.js"; -const DRAFT_07 = "https://json-schema.org/draft/2020-12/schema"; +const JSON_SCHEMA_2020_12 = "https://json-schema.org/draft/2020-12/schema"; // JSON Schema fragments as the SDK emits them. Output schemas close every // object with additionalProperties: false; input schemas leave them open. @@ -56,7 +56,7 @@ const graphOutputSchema = { relations: { type: "array", items: relationItem(true) }, }, required: ["entities", "relations"], - $schema: DRAFT_07, + $schema: JSON_SCHEMA_2020_12, additionalProperties: false, }; @@ -64,7 +64,7 @@ const statusOutputSchema = { type: "object", properties: { success: { type: "boolean" }, message: { type: "string" } }, required: ["success", "message"], - $schema: DRAFT_07, + $schema: JSON_SCHEMA_2020_12, additionalProperties: false, }; @@ -101,7 +101,7 @@ const expectedTools = [ type: "object", properties: { entities: { type: "array", items: entityItem(false) } }, required: ["entities"], - $schema: DRAFT_07, + $schema: JSON_SCHEMA_2020_12, }, outputSchema: { type: "object", @@ -110,7 +110,7 @@ const expectedTools = [ skipped: { type: "array", items: { type: "string" } }, }, required: ["entities"], - $schema: DRAFT_07, + $schema: JSON_SCHEMA_2020_12, additionalProperties: false, }, annotations: writeAnnotations, @@ -125,13 +125,13 @@ const expectedTools = [ type: "object", properties: { relations: { type: "array", items: relationItem(false) } }, required: ["relations"], - $schema: DRAFT_07, + $schema: JSON_SCHEMA_2020_12, }, outputSchema: { type: "object", properties: { relations: { type: "array", items: relationItem(true) } }, required: ["relations"], - $schema: DRAFT_07, + $schema: JSON_SCHEMA_2020_12, additionalProperties: false, }, annotations: writeAnnotations, @@ -166,7 +166,7 @@ const expectedTools = [ }, }, required: ["observations"], - $schema: DRAFT_07, + $schema: JSON_SCHEMA_2020_12, }, outputSchema: { type: "object", @@ -185,7 +185,7 @@ const expectedTools = [ }, }, required: ["results"], - $schema: DRAFT_07, + $schema: JSON_SCHEMA_2020_12, additionalProperties: false, }, annotations: writeAnnotations, @@ -206,7 +206,7 @@ const expectedTools = [ }, }, required: ["entityNames"], - $schema: DRAFT_07, + $schema: JSON_SCHEMA_2020_12, }, outputSchema: statusOutputSchema, annotations: deleteAnnotations, @@ -241,7 +241,7 @@ const expectedTools = [ }, }, required: ["deletions"], - $schema: DRAFT_07, + $schema: JSON_SCHEMA_2020_12, }, outputSchema: statusOutputSchema, annotations: deleteAnnotations, @@ -261,7 +261,7 @@ const expectedTools = [ }, }, required: ["relations"], - $schema: DRAFT_07, + $schema: JSON_SCHEMA_2020_12, }, outputSchema: statusOutputSchema, annotations: deleteAnnotations, @@ -271,7 +271,11 @@ const expectedTools = [ name: "read_graph", title: "Read Graph", description: "Read the entire knowledge graph", - inputSchema: { type: "object", properties: {}, $schema: DRAFT_07 }, + inputSchema: { + type: "object", + properties: {}, + $schema: JSON_SCHEMA_2020_12, + }, outputSchema: graphOutputSchema, annotations: readAnnotations, execution, @@ -291,7 +295,7 @@ const expectedTools = [ }, }, required: ["query"], - $schema: DRAFT_07, + $schema: JSON_SCHEMA_2020_12, }, outputSchema: graphOutputSchema, annotations: readAnnotations, @@ -311,7 +315,7 @@ const expectedTools = [ }, }, required: ["names"], - $schema: DRAFT_07, + $schema: JSON_SCHEMA_2020_12, }, outputSchema: graphOutputSchema, annotations: readAnnotations, From 019d40cdb797c36ffd75ffc2c9205c265b4990c1 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 12:42:34 -0400 Subject: [PATCH 06/65] Add purpose headers and rename two tests after the SDK v2 port Each server.py's handler and transport layer was rewritten for SDK v2, so it now opens with a purpose header stating what the file is for and the legacy-wire constraint. Two test names that pointed at the old component now name the one they exercise: call_tool's schema check, and serve_loop. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/fetch/src/mcp_server_fetch/server.py | 12 ++++++++++++ src/fetch/tests/test_protocol.py | 2 +- src/git/src/mcp_server_git/server.py | 11 +++++++++++ src/git/tests/test_server.py | 2 +- src/time/src/mcp_server_time/server.py | 10 ++++++++++ 5 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/fetch/src/mcp_server_fetch/server.py b/src/fetch/src/mcp_server_fetch/server.py index ede3bd31b0..d91a9efd17 100644 --- a/src/fetch/src/mcp_server_fetch/server.py +++ b/src/fetch/src/mcp_server_fetch/server.py @@ -1,3 +1,15 @@ +# The fetch MCP server: one `fetch` tool and one `fetch` prompt that retrieve a +# URL and convert HTML to Markdown for a model. The network policy lives here +# beside the handlers because every request path (tool, prompt and robots.txt +# check) must go through it: the private-address guard on each request and +# redirect hop, robots.txt for autonomous fetches, and proxy normalization. +# +# Built on the MCP Python SDK v2's low-level `Server` (#4851). That SDK no +# longer validates tool arguments or folds tool exceptions into `isError` +# results, so `call_tool` does both with SDK v1's messages, and `serve()` runs +# the legacy (2025-11-25) handshake loop only, keeping the wire as it was on v1. +# Serving 2026-07-28 is #4853. + import asyncio import ipaddress import os diff --git a/src/fetch/tests/test_protocol.py b/src/fetch/tests/test_protocol.py index 935e3b018e..fcb92dff6b 100644 --- a/src/fetch/tests/test_protocol.py +++ b/src/fetch/tests/test_protocol.py @@ -202,7 +202,7 @@ async def test_call_with_only_url_applies_defaults(web: FakeWeb) -> None: ({"url": PAGE, "raw": "yes"}, "'yes'"), ], ) -async def test_schema_violations_are_rejected_by_the_sdk( +async def test_schema_violations_are_rejected_by_call_tool( web: FakeWeb, arguments: dict[str, Any], offending: str ) -> None: # call_tool validates against inputSchema before the tool runs. The rest diff --git a/src/git/src/mcp_server_git/server.py b/src/git/src/mcp_server_git/server.py index 34716ec9b8..5b75260e4f 100644 --- a/src/git/src/mcp_server_git/server.py +++ b/src/git/src/mcp_server_git/server.py @@ -1,3 +1,14 @@ +# The git MCP server: tools that read and change a Git repository through +# GitPython. Repository confinement (`--repository`, `validate_repo_path`) and +# the flag-injection guards live beside the handlers because every tool call +# must pass them before any git command runs. +# +# Built on the MCP Python SDK v2's low-level `Server` (#4851). That SDK no +# longer validates tool arguments or folds tool exceptions into `isError` +# results, so `call_tool` does both with SDK v1's messages, and `serve()` runs +# the legacy (2025-11-25) handshake loop only, keeping the wire as it was on v1. +# Serving 2026-07-28 is #4853. + import logging from importlib.metadata import version from pathlib import Path diff --git a/src/git/tests/test_server.py b/src/git/tests/test_server.py index 9270d52ae0..0a6ddb0e08 100644 --- a/src/git/tests/test_server.py +++ b/src/git/tests/test_server.py @@ -852,7 +852,7 @@ def test_git_log_date_filtering(test_repository): assert len(valid_result) == 1 -async def test_serve_run_does_not_raise_exceptions(tmp_path: Path): +async def test_serve_loop_does_not_raise_exceptions(tmp_path: Path): """Verify that serve() runs the server loop without raise_exceptions=True. serve() drives SDK v2's legacy-only serve_loop rather than Server.run diff --git a/src/time/src/mcp_server_time/server.py b/src/time/src/mcp_server_time/server.py index 3bbc8690b5..8f80717cac 100644 --- a/src/time/src/mcp_server_time/server.py +++ b/src/time/src/mcp_server_time/server.py @@ -1,3 +1,13 @@ +# The time MCP server: `get_current_time` and `convert_time` over IANA +# timezones, with the local timezone detected (or set with --local-timezone) +# and named in the tool descriptions so a model can default to it. +# +# Built on the MCP Python SDK v2's low-level `Server` (#4851). That SDK no +# longer validates tool arguments or folds tool exceptions into `isError` +# results, so `call_tool` does both with SDK v1's messages, and `serve()` runs +# the legacy (2025-11-25) handshake loop only, keeping the wire as it was on v1. +# Serving 2026-07-28 is #4853. + from datetime import datetime, timedelta from importlib.metadata import version from enum import Enum From 2ca5de703ae1167adb52c9003229adf6a69f007c Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 12:43:40 -0400 Subject: [PATCH 07/65] chore(changeset): drop pending notes for the task tools #4856 removes everything-async-task-tools.md described fixes to two tools this release no longer ships, and everything-conditional-tools-4792.md named one of them; keep only what consumers receive (Copilot round 2 on #5063). Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .changeset/everything-async-task-tools.md | 5 ----- .changeset/everything-conditional-tools-4792.md | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 .changeset/everything-async-task-tools.md diff --git a/.changeset/everything-async-task-tools.md b/.changeset/everything-async-task-tools.md deleted file mode 100644 index ed0f3bc84a..0000000000 --- a/.changeset/everything-async-task-tools.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-everything": patch ---- - -Fix the async task tools: `trigger-elicitation-request-async` now stops polling before the 10-minute TTL it requests for the client's task runs out, instead of polling past it and failing on a task the client has expired (#4986); and `trigger-sampling-request-async` reports the status message of a client task that is already finished when it is created, instead of "No message" (#4987). `trigger-elicitation-request-async` had the same flaw and gets the same fix. diff --git a/.changeset/everything-conditional-tools-4792.md b/.changeset/everything-conditional-tools-4792.md index 7e9a6c5656..86d7a5c556 100644 --- a/.changeset/everything-conditional-tools-4792.md +++ b/.changeset/everything-conditional-tools-4792.md @@ -2,4 +2,4 @@ "@modelcontextprotocol/server-everything": patch --- -Server instructions no longer tell every client to use tools that are registered only for clients declaring a capability; they list those tools and the capability each needs (#4792, ported from #4835). `trigger-elicitation-request` and `trigger-elicitation-request-async` are now registered only for clients that support form-mode elicitation, so a client that declared only URL-mode elicitation no longer sees tools it cannot answer (#4985). +Server instructions no longer tell every client to use tools that are registered only for clients declaring a capability; they list those tools and the capability each needs (#4792, ported from #4835). `trigger-elicitation-request` is now registered only for clients that support form-mode elicitation, so a client that declared only URL-mode elicitation no longer sees tools it cannot answer (#4985). From 92eca9c72b54fbf4a02e0bf7251d55c101af67a2 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 12:43:50 -0400 Subject: [PATCH 08/65] chore(changeset): match the conditional-tools note to its one remaining tool Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .changeset/everything-conditional-tools-4792.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/everything-conditional-tools-4792.md b/.changeset/everything-conditional-tools-4792.md index 86d7a5c556..0b07bc7238 100644 --- a/.changeset/everything-conditional-tools-4792.md +++ b/.changeset/everything-conditional-tools-4792.md @@ -2,4 +2,4 @@ "@modelcontextprotocol/server-everything": patch --- -Server instructions no longer tell every client to use tools that are registered only for clients declaring a capability; they list those tools and the capability each needs (#4792, ported from #4835). `trigger-elicitation-request` is now registered only for clients that support form-mode elicitation, so a client that declared only URL-mode elicitation no longer sees tools it cannot answer (#4985). +Server instructions no longer tell every client to use tools that are registered only for clients declaring a capability; they list those tools and the capability each needs (#4792, ported from #4835). `trigger-elicitation-request` is now registered only for clients that support form-mode elicitation, so a client that declared only URL-mode elicitation no longer sees a tool it cannot answer (#4985). From 623a8dfd436fa1a2b54c791902527fc22a742962 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 12:49:09 -0400 Subject: [PATCH 09/65] Call out the unknown-method error code change from SDK v2 SDK v1 answered every unknown method with -32602 "Invalid request parameters" (its request union failed to parse); SDK v2 answers -32601 "Method not found", the code JSON-RPC defines. The era tests' comments claimed v1 parity, and the server headers and serve() comments claimed an unchanged wire. Correct them to say what holds (tool results are unchanged) and record the error code as a behavior change. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/fetch/src/mcp_server_fetch/server.py | 10 +++++----- src/fetch/tests/test_protocol.py | 7 ++++++- src/git/src/mcp_server_git/server.py | 10 +++++----- src/git/tests/test_protocol.py | 7 ++++++- src/time/src/mcp_server_time/server.py | 10 +++++----- src/time/tests/test_protocol.py | 7 ++++++- 6 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/fetch/src/mcp_server_fetch/server.py b/src/fetch/src/mcp_server_fetch/server.py index d91a9efd17..7c338f839e 100644 --- a/src/fetch/src/mcp_server_fetch/server.py +++ b/src/fetch/src/mcp_server_fetch/server.py @@ -6,9 +6,9 @@ # # Built on the MCP Python SDK v2's low-level `Server` (#4851). That SDK no # longer validates tool arguments or folds tool exceptions into `isError` -# results, so `call_tool` does both with SDK v1's messages, and `serve()` runs -# the legacy (2025-11-25) handshake loop only, keeping the wire as it was on v1. -# Serving 2026-07-28 is #4853. +# results, so `call_tool` does both with SDK v1's messages, keeping tool +# results as they were on v1. `serve()` runs the legacy (2025-11-25) handshake +# loop only; serving 2026-07-28 is #4853. import asyncio import ipaddress @@ -488,7 +488,7 @@ async def call_tool( ) -> CallToolResult: # SDK v2's low-level server neither validates arguments nor turns a # handler exception into an isError result, both of which SDK v1 did. - # Both are done here, with v1's messages, so the wire is unchanged. + # Both are done here, with v1's messages, so tool results are unchanged. arguments = params.arguments or {} if params.name == tool.name: try: @@ -552,7 +552,7 @@ async def get_prompt( # Legacy era only. Server.run() would also serve 2026-07-28 (its # dual-era loop answers server/discover and per-request envelopes); # adopting that era is #4853, so this port (#4851) serves the - # handshake loop alone and keeps the wire unchanged. + # handshake loop alone, the only era it served on SDK v1. async with server.lifespan(server) as lifespan_state: await serve_loop( server, diff --git a/src/fetch/tests/test_protocol.py b/src/fetch/tests/test_protocol.py index fcb92dff6b..0a2734a19b 100644 --- a/src/fetch/tests/test_protocol.py +++ b/src/fetch/tests/test_protocol.py @@ -1016,7 +1016,12 @@ async def test_modern_era_is_not_served() -> None: # #4851: the SDK v2 port serves the 2025-11-25 handshake era only. # Server.run() would also open a 2026-07-28 connection for a request that # carries the per-request `_meta` envelope; adopting that era is #4853. - # Until then such a request is refused as it was on SDK v1. + # Until then such a request is refused as an unknown method. + # + # Behavior change in the SDK v2 port: SDK v1 answered every unknown + # method with -32602 "Invalid request parameters" (its request union + # failed to parse); SDK v2 answers -32601 "Method not found", the code + # JSON-RPC defines for it. discover = JSONRPCRequest( jsonrpc="2.0", id=1, diff --git a/src/git/src/mcp_server_git/server.py b/src/git/src/mcp_server_git/server.py index 5b75260e4f..90262e8b55 100644 --- a/src/git/src/mcp_server_git/server.py +++ b/src/git/src/mcp_server_git/server.py @@ -5,9 +5,9 @@ # # Built on the MCP Python SDK v2's low-level `Server` (#4851). That SDK no # longer validates tool arguments or folds tool exceptions into `isError` -# results, so `call_tool` does both with SDK v1's messages, and `serve()` runs -# the legacy (2025-11-25) handshake loop only, keeping the wire as it was on v1. -# Serving 2026-07-28 is #4853. +# results, so `call_tool` does both with SDK v1's messages, keeping tool +# results as they were on v1. `serve()` runs the legacy (2025-11-25) handshake +# loop only; serving 2026-07-28 is #4853. import logging from importlib.metadata import version @@ -751,7 +751,7 @@ async def call_tool( ) -> CallToolResult: # SDK v2's low-level server neither validates arguments nor turns a # handler exception into an isError result, both of which SDK v1 did. - # Both are done here, with v1's messages, so the wire is unchanged. + # Both are done here, with v1's messages, so tool results are unchanged. arguments = params.arguments or {} schema = schemas.get(params.name) if schema is not None: @@ -775,7 +775,7 @@ async def call_tool( # Legacy era only. Server.run() would also serve 2026-07-28 (its # dual-era loop answers server/discover and per-request envelopes); # adopting that era is #4853, so this port (#4851) serves the - # handshake loop alone and keeps the wire unchanged. + # handshake loop alone, the only era it served on SDK v1. async with server.lifespan(server) as lifespan_state: await serve_loop( server, diff --git a/src/git/tests/test_protocol.py b/src/git/tests/test_protocol.py index 77a054b2a9..c9b8e5b3e6 100644 --- a/src/git/tests/test_protocol.py +++ b/src/git/tests/test_protocol.py @@ -1165,7 +1165,12 @@ async def test_modern_era_is_not_served() -> None: # #4851: the SDK v2 port serves the 2025-11-25 handshake era only. # Server.run() would also open a 2026-07-28 connection for a request that # carries the per-request `_meta` envelope; adopting that era is #4853. - # Until then such a request is refused as it was on SDK v1. + # Until then such a request is refused as an unknown method. + # + # Behavior change in the SDK v2 port: SDK v1 answered every unknown + # method with -32602 "Invalid request parameters" (its request union + # failed to parse); SDK v2 answers -32601 "Method not found", the code + # JSON-RPC defines for it. discover = JSONRPCRequest( jsonrpc="2.0", id=1, diff --git a/src/time/src/mcp_server_time/server.py b/src/time/src/mcp_server_time/server.py index 8f80717cac..9f56bb28fa 100644 --- a/src/time/src/mcp_server_time/server.py +++ b/src/time/src/mcp_server_time/server.py @@ -4,9 +4,9 @@ # # Built on the MCP Python SDK v2's low-level `Server` (#4851). That SDK no # longer validates tool arguments or folds tool exceptions into `isError` -# results, so `call_tool` does both with SDK v1's messages, and `serve()` runs -# the legacy (2025-11-25) handshake loop only, keeping the wire as it was on v1. -# Serving 2026-07-28 is #4853. +# results, so `call_tool` does both with SDK v1's messages, keeping tool +# results as they were on v1. `serve()` runs the legacy (2025-11-25) handshake +# loop only; serving 2026-07-28 is #4853. from datetime import datetime, timedelta from importlib.metadata import version @@ -282,7 +282,7 @@ async def call_tool( ) -> CallToolResult: # SDK v2's low-level server neither validates arguments nor turns a # handler exception into an isError result, both of which SDK v1 did. - # Both are done here, with v1's messages, so the wire is unchanged. + # Both are done here, with v1's messages, so tool results are unchanged. arguments = params.arguments or {} schema = schemas.get(params.name) if schema is not None: @@ -306,7 +306,7 @@ async def call_tool( # Legacy era only. Server.run() would also serve 2026-07-28 (its # dual-era loop answers server/discover and per-request envelopes); # adopting that era is #4853, so this port (#4851) serves the - # handshake loop alone and keeps the wire unchanged. + # handshake loop alone, the only era it served on SDK v1. async with server.lifespan(server) as lifespan_state: await serve_loop( server, diff --git a/src/time/tests/test_protocol.py b/src/time/tests/test_protocol.py index de5b7aa649..e2ab1d6384 100644 --- a/src/time/tests/test_protocol.py +++ b/src/time/tests/test_protocol.py @@ -933,7 +933,12 @@ async def test_modern_era_is_not_served() -> None: # #4851: the SDK v2 port serves the 2025-11-25 handshake era only. # Server.run() would also open a 2026-07-28 connection for a request that # carries the per-request `_meta` envelope; adopting that era is #4853. - # Until then such a request is refused as it was on SDK v1. + # Until then such a request is refused as an unknown method. + # + # Behavior change in the SDK v2 port: SDK v1 answered every unknown + # method with -32602 "Invalid request parameters" (its request union + # failed to parse); SDK v2 answers -32601 "Method not found", the code + # JSON-RPC defines for it. discover = JSONRPCRequest( jsonrpc="2.0", id=1, From 4f39a5456ad2176d0bdd790019154302da53a676 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 12:52:46 -0400 Subject: [PATCH 10/65] fix: address Copilot round 3 on #5063 State the Node.js 20 floor in each server's README and note in everything's that the task tools are out until #4852. Assert that sequentialthinking advertises no execution metadata rather than skipping the test, and correct a stale v1 comment in sampling-elicitation.test.ts. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/everything/README.md | 6 ++++++ src/everything/__tests__/sampling-elicitation.test.ts | 3 ++- src/filesystem/README.md | 4 ++++ src/memory/README.md | 4 ++++ src/sequentialthinking/README.md | 4 ++++ src/sequentialthinking/__tests__/tools-list.test.ts | 10 ++++++---- 6 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/everything/README.md b/src/everything/README.md index f061e946a2..8fa2d2ebdd 100644 --- a/src/everything/README.md +++ b/src/everything/README.md @@ -13,6 +13,12 @@ This MCP server attempts to exercise all the features of the MCP protocol. It is A complete list of the registered MCP primitives and other protocol features demonstrated can be found in the [Server Features](docs/features.md) document. +The task tools (`simulate-research-query`, `trigger-sampling-request-async` and `trigger-elicitation-request-async`) are not available for now: they were built on the TypeScript SDK's experimental tasks layer, which SDK v2 removed. They return on the MCP tasks extension in [#4852](https://github.com/modelcontextprotocol/servers/issues/4852). + +## Requirements + +Node.js 20 or later, when the server runs through `npx` or from source. + ## Usage with Claude Desktop (uses [stdio Transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#stdio)) Add to your `claude_desktop_config.json`: diff --git a/src/everything/__tests__/sampling-elicitation.test.ts b/src/everything/__tests__/sampling-elicitation.test.ts index 4acbc272e1..cbb46e370c 100644 --- a/src/everything/__tests__/sampling-elicitation.test.ts +++ b/src/everything/__tests__/sampling-elicitation.test.ts @@ -479,7 +479,8 @@ describe("trigger-url-elicitation", () => { expect(error).toBeInstanceOf(ProtocolError); const mcpError = error as ProtocolError; expect(mcpError.code).toBe(-32042); - // The SDK prefixes the code twice on the client side. + // SDK v2 passes the server's message through verbatim (SDK 1.x prefixed + // "MCP error -32042: " twice on the client side). expect(mcpError.message).toBe( "This request requires browser-based authorization.", ); diff --git a/src/filesystem/README.md b/src/filesystem/README.md index d00dc9abce..8b705087dc 100644 --- a/src/filesystem/README.md +++ b/src/filesystem/README.md @@ -216,6 +216,10 @@ The mapping for filesystem tools is: > Note: `idempotentHint` and `destructiveHint` are meaningful only when `readOnlyHint` is `false`, as defined by the MCP spec. Every tool also sets `openWorldHint: false` — this server only accesses the local filesystem within its allowed directories, never an open or external world. +## Requirements + +Node.js 20 or later, when the server runs through `npx` or from source. + ## Usage with Claude Desktop Add this to your `claude_desktop_config.json`: diff --git a/src/memory/README.md b/src/memory/README.md index e5ac259fbe..ab911d4885 100644 --- a/src/memory/README.md +++ b/src/memory/README.md @@ -137,6 +137,10 @@ Example: - Returns the same shape as `read_graph` (entities and relations) - Mutation tools (`create_entities`, `create_relations`, `add_observations`, `delete_entities`, `delete_observations`, `delete_relations`) emit `notifications/resources/updated` for this URI, so subscribed clients see live changes +## Requirements + +Node.js 20 or later, when the server runs through `npx` or from source. + # Usage with Claude Desktop ### Setup diff --git a/src/sequentialthinking/README.md b/src/sequentialthinking/README.md index b6b853aa56..cbb15f7329 100644 --- a/src/sequentialthinking/README.md +++ b/src/sequentialthinking/README.md @@ -69,6 +69,10 @@ After installing the server in your MCP host: 3. Ask the host to solve a non-trivial problem in a step-by-step way. 4. Verify that the host invokes the tool multiple times instead of returning a one-shot answer. +## Requirements + +Node.js 20 or later, when the server runs through `npx` or from source. + ## Configuration ### Usage with Claude Desktop diff --git a/src/sequentialthinking/__tests__/tools-list.test.ts b/src/sequentialthinking/__tests__/tools-list.test.ts index 69ef0455a4..38d0b2da9e 100644 --- a/src/sequentialthinking/__tests__/tools-list.test.ts +++ b/src/sequentialthinking/__tests__/tools-list.test.ts @@ -64,10 +64,12 @@ describe("sequentialthinking: initialize and tools/list", () => { }); }); - // SDK v2 removed the experimental tasks layer and no longer advertises - // `execution.taskSupport`; Part 5 (#4852) restores task semantics. - it.skip("forbids task-augmented execution", () => { - expect(tool.execution).toEqual({ taskSupport: "forbidden" }); + // SDK 1.x advertised `execution: { taskSupport: "forbidden" }`. SDK v2 + // removed the experimental tasks layer and advertises no `execution` at + // all; Part 5 (#4852) decides what the tool advertises under the tasks + // extension. + it("advertises no task-execution metadata", () => { + expect(tool.execution).toBeUndefined(); }); // #799: some clients cap a tool description at 1024 characters. This one is From 701c2aa8eeb3b511ce60369d6d36083fa0ca990d Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 13:09:00 -0400 Subject: [PATCH 11/65] chore(changeset): record SDK v2's 10 MiB stdio message cap (#4856) Copilot round 4 on #5063: v2's StdioServerTransport closes the connection on a single message over 10 MiB, where 1.x buffered without limit. Keep the SDK default and list it among the breaking changes. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .changeset/everything-sdk-v2.md | 2 +- .changeset/filesystem-sdk-v2.md | 2 +- .changeset/memory-sdk-v2.md | 2 +- .changeset/sequentialthinking-sdk-v2.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.changeset/everything-sdk-v2.md b/.changeset/everything-sdk-v2.md index 39558eefc6..07fe4f38ae 100644 --- a/.changeset/everything-sdk-v2.md +++ b/.changeset/everything-sdk-v2.md @@ -2,4 +2,4 @@ "@modelcontextprotocol/server-everything": major --- -Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`, `@modelcontextprotocol/node`, and `@modelcontextprotocol/server-legacy` for the deprecated HTTP+SSE transport) from `@modelcontextprotocol/sdk` 1.x (#4856). The task tools `simulate-research-query`, `trigger-sampling-request-async` and `trigger-elicitation-request-async` are removed, and the server no longer advertises the `tasks` capability, because SDK v2 dropped the experimental tasks layer they were built on; they return on the tasks extension in #4852. Breaking for clients: the server now requires Node.js 20 or later; an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. The optional `state` argument of `args-prompt` now carries its description. +Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`, `@modelcontextprotocol/node`, and `@modelcontextprotocol/server-legacy` for the deprecated HTTP+SSE transport) from `@modelcontextprotocol/sdk` 1.x (#4856). The task tools `simulate-research-query`, `trigger-sampling-request-async` and `trigger-elicitation-request-async` are removed, and the server no longer advertises the `tasks` capability, because SDK v2 dropped the experimental tasks layer they were built on; they return on the tasks extension in #4852. Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. The optional `state` argument of `args-prompt` now carries its description. diff --git a/.changeset/filesystem-sdk-v2.md b/.changeset/filesystem-sdk-v2.md index c39ccdf732..523a805d57 100644 --- a/.changeset/filesystem-sdk-v2.md +++ b/.changeset/filesystem-sdk-v2.md @@ -2,4 +2,4 @@ "@modelcontextprotocol/server-filesystem": major --- -Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x (#4856). Breaking for clients: the server now requires Node.js 20 or later; an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); and tools no longer advertise `execution.taskSupport`. +Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x (#4856). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); and tools no longer advertise `execution.taskSupport`. diff --git a/.changeset/memory-sdk-v2.md b/.changeset/memory-sdk-v2.md index 9d0b41c03b..e1ab5b57df 100644 --- a/.changeset/memory-sdk-v2.md +++ b/.changeset/memory-sdk-v2.md @@ -2,4 +2,4 @@ "@modelcontextprotocol/server-memory": major --- -Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x (#4856). Breaking for clients: the server now requires Node.js 20 or later; an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. +Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x (#4856). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. diff --git a/.changeset/sequentialthinking-sdk-v2.md b/.changeset/sequentialthinking-sdk-v2.md index 4965463dd1..9e76a69e93 100644 --- a/.changeset/sequentialthinking-sdk-v2.md +++ b/.changeset/sequentialthinking-sdk-v2.md @@ -2,4 +2,4 @@ "@modelcontextprotocol/server-sequential-thinking": major --- -Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x (#4856). Breaking for clients: the server now requires Node.js 20 or later; an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. +Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x (#4856). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. From ff6d4e19312b11017b7be653445980e19b87e6d1 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 13:16:17 -0400 Subject: [PATCH 12/65] docs(everything): name the v2 transports in structure.md (#4856) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/everything/docs/structure.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/everything/docs/structure.md b/src/everything/docs/structure.md index 9aac31847d..2464b1c7c8 100644 --- a/src/everything/docs/structure.md +++ b/src/everything/docs/structure.md @@ -194,10 +194,10 @@ src/everything - `GET /sse` to establish an SSE connection per session. - `POST /message` for client messages. - Manages multiple connected clients via a transport map. - - Starts an `SSEServerTransport`, created the server via `createServer()`, and connects it to a new transport. + - Starts an `SSEServerTransport` (from the frozen `@modelcontextprotocol/server-legacy/sse` bridge), created the server via `createServer()`, and connects it to a new transport. - On server disconnect, calls `cleanup()` to remove any live intervals. - `streamableHttp.ts` - - Express server exposing a single `/mcp` endpoint for POST (JSON‑RPC), GET (SSE stream), and DELETE (session termination) using `StreamableHTTPServerTransport`. + - Express server exposing a single `/mcp` endpoint for POST (JSON‑RPC), GET (SSE stream), and DELETE (session termination) using `NodeStreamableHTTPServerTransport` from `@modelcontextprotocol/node`. - Uses an `InMemoryEventStore` for resumable sessions and tracks transports by `sessionId`. - Answers a request for an unknown or ended `Mcp-Session-Id` with `404 Not Found`, and closes every open session on `SIGINT`. - Connects a fresh server instance on initialization POST and reuses the transport for subsequent requests. From d0d81b1123533459bc5c792fb3d8576b4ee203dd Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Wed, 28 Jan 2026 17:56:25 +0100 Subject: [PATCH 13/65] ci: add MCP interface diff workflow for Everything server Adds a GitHub Actions workflow that tracks public interface changes to the Everything MCP server using mcp-server-diff. Features: - Runs on PRs and pushes affecting src/everything/ - Auto-compares against merge-base (PRs) or previous state (pushes) - Manual workflow_dispatch for comparing any two refs - Generates diff reports showing tool, resource, prompt, and capability changes This helps catch unintended interface changes and provides clear visibility into how the reference server evolves over time. Related: https://github.com/modelcontextprotocol/inspector/issues/1034 Signed-off-by: cliffhall --- .github/workflows/everything-mcp-diff.yml | 70 +++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 .github/workflows/everything-mcp-diff.yml diff --git a/.github/workflows/everything-mcp-diff.yml b/.github/workflows/everything-mcp-diff.yml new file mode 100644 index 0000000000..6dd2db00e5 --- /dev/null +++ b/.github/workflows/everything-mcp-diff.yml @@ -0,0 +1,70 @@ +# Tracks public interface changes to the Everything MCP server. +# The Everything server is a reference implementation demonstrating all MCP features. +# This workflow helps reviewers see how tools, resources, prompts, and capabilities +# evolve over time - useful for SDK compliance validation and catching regressions. +# +# See: https://github.com/modelcontextprotocol/inspector/issues/1034 +name: Everything Server MCP Diff + +on: + push: + branches: [main] + paths: + - 'src/everything/**' + pull_request: + paths: + - 'src/everything/**' + # Manual trigger for comparing any two refs (commits, tags, branches) + workflow_dispatch: + inputs: + compare_ref: + description: 'Base ref to compare against (commit SHA, tag, or branch)' + required: false + default: '' + target_ref: + description: 'Target ref to compare (defaults to current branch if empty)' + required: false + default: '' + +permissions: + contents: read + +jobs: + mcp-diff: + name: Diff Everything Server Interface + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + ref: ${{ github.event.inputs.target_ref || github.ref }} + + - name: Run MCP Server Diff + uses: SamMorrowDrums/mcp-server-diff@4ddeb31dd8b3f98f2c20d24d60d4d9b2b9aa6e3b # v2.1.1 + with: + setup_node: 'true' + node_version: '22' + install_command: npm ci + build_command: npm run build + start_command: node dist/index.js stdio + compare_ref: ${{ github.event.inputs.compare_ref || '' }} + server_timeout: '15' + working-directory: src/everything + + - name: Add summary context + if: always() + run: | + echo "" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "ℹ️ **Interpreting Results**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "The Everything server is a reference implementation that exercises all MCP features." >> $GITHUB_STEP_SUMMARY + echo "Interface changes here may indicate:" >> $GITHUB_STEP_SUMMARY + echo "- New MCP protocol features being demonstrated" >> $GITHUB_STEP_SUMMARY + echo "- Updated tool schemas or descriptions" >> $GITHUB_STEP_SUMMARY + echo "- New resources, prompts, or capabilities" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Review changes to ensure they align with the intended protocol updates." >> $GITHUB_STEP_SUMMARY From 4948cdc8a0faa90b832aec39cedb9b887d577e91 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Wed, 28 Jan 2026 18:09:02 +0100 Subject: [PATCH 14/65] fix: update mcp-server-diff to v2.3.5 Signed-off-by: cliffhall --- .github/workflows/everything-mcp-diff.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/everything-mcp-diff.yml b/.github/workflows/everything-mcp-diff.yml index 6dd2db00e5..3473f664a9 100644 --- a/.github/workflows/everything-mcp-diff.yml +++ b/.github/workflows/everything-mcp-diff.yml @@ -42,7 +42,7 @@ jobs: ref: ${{ github.event.inputs.target_ref || github.ref }} - name: Run MCP Server Diff - uses: SamMorrowDrums/mcp-server-diff@4ddeb31dd8b3f98f2c20d24d60d4d9b2b9aa6e3b # v2.1.1 + uses: SamMorrowDrums/mcp-server-diff@a5555e85d68eaa014a334ae7d12b73787f2c49cc # v2.3.5 with: setup_node: 'true' node_version: '22' From e245576e30bd100bac484b54fd1cb5b17878c581 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Wed, 28 Jan 2026 18:17:34 +0100 Subject: [PATCH 15/65] fix: use full paths instead of working-directory Composite actions don't support working-directory at step level. Use full paths in commands instead. Signed-off-by: cliffhall --- .github/workflows/everything-mcp-diff.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/everything-mcp-diff.yml b/.github/workflows/everything-mcp-diff.yml index 3473f664a9..16168537c3 100644 --- a/.github/workflows/everything-mcp-diff.yml +++ b/.github/workflows/everything-mcp-diff.yml @@ -46,12 +46,11 @@ jobs: with: setup_node: 'true' node_version: '22' - install_command: npm ci - build_command: npm run build - start_command: node dist/index.js stdio + install_command: cd src/everything && npm ci + build_command: cd src/everything && npm run build + start_command: node src/everything/dist/index.js stdio compare_ref: ${{ github.event.inputs.compare_ref || '' }} server_timeout: '15' - working-directory: src/everything - name: Add summary context if: always() From 9f05f499add22661dad6ab86a2a3266c5d7bda50 Mon Sep 17 00:00:00 2001 From: SamMorrowDrums <48113580+SamMorrowDrums@users.noreply.github.com> Date: Wed, 10 Jun 2026 21:43:02 +0200 Subject: [PATCH 16/65] ci: add concurrency group and fix mcp-server-diff SHA pin Address review feedback: - Add concurrency group so superseded runs on the same ref are cancelled - Repin SamMorrowDrums/mcp-server-diff to the correct v2.3.5 commit SHA (the previous SHA did not exist in the action's repo) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: cliffhall --- .github/workflows/everything-mcp-diff.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/everything-mcp-diff.yml b/.github/workflows/everything-mcp-diff.yml index 16168537c3..49dfee3064 100644 --- a/.github/workflows/everything-mcp-diff.yml +++ b/.github/workflows/everything-mcp-diff.yml @@ -29,6 +29,10 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: mcp-diff: name: Diff Everything Server Interface @@ -42,7 +46,7 @@ jobs: ref: ${{ github.event.inputs.target_ref || github.ref }} - name: Run MCP Server Diff - uses: SamMorrowDrums/mcp-server-diff@a5555e85d68eaa014a334ae7d12b73787f2c49cc # v2.3.5 + uses: SamMorrowDrums/mcp-server-diff@f7e5e58a4b0c4f68a5827adad2292953bb1ab9ef # v2.3.5 with: setup_node: 'true' node_version: '22' From 124b28ff1409f32a86484525b892bdaa6e3e0efc Mon Sep 17 00:00:00 2001 From: SamMorrowDrums <48113580+SamMorrowDrums@users.noreply.github.com> Date: Wed, 10 Jun 2026 22:04:00 +0200 Subject: [PATCH 17/65] ci: post sticky PR comment with MCP diff report Surface the Everything server interface diff directly on the PR instead of requiring reviewers to dig into the Actions tab. - Add 'pull-requests: write' permission - Capture mcp-server-diff status output via step id - Build a comment body that summarises the status and includes the full report in a collapsed
block - Post via marocchino/sticky-pull-request-comment (SHA-pinned to v3.0.4) using a stable header so subsequent pushes update the same comment - Guarded to same-repo PRs only; fork PRs still get the summary and uploaded artifact Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: cliffhall --- .github/workflows/everything-mcp-diff.yml | 42 +++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/.github/workflows/everything-mcp-diff.yml b/.github/workflows/everything-mcp-diff.yml index 49dfee3064..0eabe2264e 100644 --- a/.github/workflows/everything-mcp-diff.yml +++ b/.github/workflows/everything-mcp-diff.yml @@ -28,6 +28,7 @@ on: permissions: contents: read + pull-requests: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -46,6 +47,7 @@ jobs: ref: ${{ github.event.inputs.target_ref || github.ref }} - name: Run MCP Server Diff + id: mcp_diff uses: SamMorrowDrums/mcp-server-diff@f7e5e58a4b0c4f68a5827adad2292953bb1ab9ef # v2.3.5 with: setup_node: 'true' @@ -71,3 +73,43 @@ jobs: echo "- New resources, prompts, or capabilities" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "Review changes to ensure they align with the intended protocol updates." >> $GITHUB_STEP_SUMMARY + + # Post (or update) a sticky PR comment so reviewers can see the diff without + # navigating to the Actions tab. The same comment is updated on subsequent + # pushes via the `header` key. Skipped for PRs from forks where GITHUB_TOKEN + # is read-only — the report is still available in the workflow summary and + # as an uploaded artifact. + - name: Build PR comment body + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository + id: comment_body + run: | + { + echo 'body<Full report' + echo '' + tail -n +5 conformance-report/CONFORMANCE_REPORT.md + echo '' + echo '
' + else + echo '_Report file not found — see the workflow logs for details._' + fi + echo '' + echo "Updated by [\`${{ github.workflow }}\`](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) · commit \`${{ github.event.pull_request.head.sha }}\`" + echo 'MCP_DIFF_EOF' + } >> "$GITHUB_OUTPUT" + + - name: Post / update sticky PR comment + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository + uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 + with: + header: everything-server-mcp-diff + message: ${{ steps.comment_body.outputs.body }} From afc07115bd6883c72613fb020cd8877d18ba51ce Mon Sep 17 00:00:00 2001 From: SamMorrowDrums <48113580+SamMorrowDrums@users.noreply.github.com> Date: Wed, 10 Jun 2026 22:16:35 +0200 Subject: [PATCH 18/65] ci: also run MCP diff on TypeScript release tags Mirrors github/github-mcp-server's pattern of pushing on tags so each release surfaces its cumulative interface delta. The everything server ships as part of the typescript-servers monorepo bundle, so we trigger on typescript-servers-* tags and let mcp-server-diff auto-compare against the previous matching tag. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: cliffhall --- .github/workflows/everything-mcp-diff.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/everything-mcp-diff.yml b/.github/workflows/everything-mcp-diff.yml index 0eabe2264e..798367fe8e 100644 --- a/.github/workflows/everything-mcp-diff.yml +++ b/.github/workflows/everything-mcp-diff.yml @@ -11,6 +11,11 @@ on: branches: [main] paths: - 'src/everything/**' + # TypeScript release tags also trigger a run, comparing against the + # previous typescript-servers-* tag (auto-detected by mcp-server-diff). + # This surfaces the cumulative interface delta for each release. + tags: + - 'typescript-servers-*' pull_request: paths: - 'src/everything/**' From e5ca90c71eed9ecdd791a7e541730011b3412ac5 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sun, 4 Oct 2026 02:13:33 -0400 Subject: [PATCH 19/65] ci(everything): run the interface diff as an npm script on v2/main (#4860) Adapt #3260's workflow for v2/main: - Run the check as `npm run interface-diff` (scripts/interface-diff.mjs), driving the mcp-server-diff 3.0.0 CLI as an exact-pinned root devDependency instead of the SHA-pinned v2.3.5 action. The same script is a new last stage of local:gate, which workflow-gate.test.mjs now requires, so the gate keeps running every check CI runs. - Choose the base per event (the PR's base, the commit before a push, the previous release tag, or a dispatch input) instead of the tool's default merge-base with origin/main, which is wrong for v2/main PRs. - Trigger pushes on main and v2/main, PRs on any base, and published Releases instead of the retired typescript-servers-* tags. - Run the PR's code only in a read-only job with no persisted credentials; post the sticky comment from a separate job that holds pull-requests: write, runs no PR code, and only runs for same-repo PRs. Fork PRs keep the job summary and the report artifact. - Give every job a timeout. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .github/workflows/everything-mcp-diff.yml | 255 +++++++++++----- package-lock.json | 93 ++++++ package.json | 4 +- scripts/interface-diff.mjs | 345 ++++++++++++++++++++++ scripts/interface-diff.test.mjs | 127 ++++++++ scripts/lib/workflow-gate.test.mjs | 1 + 6 files changed, 749 insertions(+), 76 deletions(-) create mode 100644 scripts/interface-diff.mjs create mode 100644 scripts/interface-diff.test.mjs diff --git a/.github/workflows/everything-mcp-diff.yml b/.github/workflows/everything-mcp-diff.yml index 798367fe8e..30f556c8e6 100644 --- a/.github/workflows/everything-mcp-diff.yml +++ b/.github/workflows/everything-mcp-diff.yml @@ -3,118 +3,223 @@ # This workflow helps reviewers see how tools, resources, prompts, and capabilities # evolve over time - useful for SDK compliance validation and catching regressions. # +# Contributed by @SamMorrowDrums in #3260 and adapted for v2/main in #4860: +# +# - The check is `npm run interface-diff` (scripts/interface-diff.mjs), which +# drives the `mcp-server-diff` CLI, an exact-pinned root devDependency. The +# same script is a `local:gate` stage, and `scripts/lib/workflow-gate.test.mjs` +# requires every npm script a workflow runs to be one, so CI and the gate run +# the same check. No third-party action runs here. +# - The base is chosen per event below rather than by the tool, whose default +# (the merge-base with origin/main) is the wrong base for PRs to v2/main. +# - Untrusted code (the PR's install, build and server) runs only in `diff`, +# which holds a read-only token and no persisted credentials. The sticky +# comment is posted by `comment`, which holds `pull-requests: write` and runs +# none of the PR's code, and only for PRs from this repository: a fork PR's +# token is read-only, so there the report is in the job summary and the +# `mcp-diff-report` artifact. `pull_request_target` is deliberately not used, +# since it would run the fork's build next to a write token. If fork PRs need +# the comment too, the way is a separate `workflow_run` workflow that posts +# the artifact without checking anything out. +# +# A changed interface does not fail the run: the diff is information for the +# reviewer. A diff that cannot run (a base that does not build, a server that +# cannot be probed) does. +# # See: https://github.com/modelcontextprotocol/inspector/issues/1034 name: Everything Server MCP Diff on: push: - branches: [main] + branches: [main, v2/main] paths: - - 'src/everything/**' - # TypeScript release tags also trigger a run, comparing against the - # previous typescript-servers-* tag (auto-detected by mcp-server-diff). - # This surfaces the cumulative interface delta for each release. - tags: - - 'typescript-servers-*' + - "src/everything/**" + - "package.json" + - "package-lock.json" + - "scripts/interface-diff.mjs" + - ".github/workflows/everything-mcp-diff.yml" + # No branch filter: PRs to v2/main and to main both run, and so does a PR + # stacked on another PR's branch. The base is the PR's own base either way. pull_request: paths: - - 'src/everything/**' + - "src/everything/**" + - "package.json" + - "package-lock.json" + - "scripts/interface-diff.mjs" + - ".github/workflows/everything-mcp-diff.yml" + # A published Release (a milestone, tagged vX.Y.Z on main; see RELEASING.md) + # surfaces the cumulative interface delta since the previous release's tag. + release: + types: [published] # Manual trigger for comparing any two refs (commits, tags, branches) workflow_dispatch: inputs: compare_ref: - description: 'Base ref to compare against (commit SHA, tag, or branch)' + description: "Base ref to compare against (commit SHA, tag, or branch; defaults to the merge-base with v2/main)" required: false - default: '' + default: "" target_ref: - description: 'Target ref to compare (defaults to current branch if empty)' + description: "Target ref to compare (defaults to the ref the workflow runs on)" required: false - default: '' + default: "" permissions: contents: read - pull-requests: write +# Superseded runs on the same PR are cancelled; pushes and releases each keep +# their own run. concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: - mcp-diff: + diff: name: Diff Everything Server Interface + timeout-minutes: 15 runs-on: ubuntu-latest - + permissions: + contents: read steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@v6 with: + # The base is built from history, so all of it is needed, tags + # included (the release base is the previous tag). fetch-depth: 0 - ref: ${{ github.event.inputs.target_ref || github.ref }} + ref: ${{ inputs.target_ref || github.ref }} + persist-credentials: false - - name: Run MCP Server Diff - id: mcp_diff - uses: SamMorrowDrums/mcp-server-diff@f7e5e58a4b0c4f68a5827adad2292953bb1ab9ef # v2.3.5 + - uses: actions/setup-node@v7 with: - setup_node: 'true' - node_version: '22' - install_command: cd src/everything && npm ci - build_command: cd src/everything && npm run build - start_command: node src/everything/dist/index.js stdio - compare_ref: ${{ github.event.inputs.compare_ref || '' }} - server_timeout: '15' + node-version: 22 + cache: npm - - name: Add summary context - if: always() + # A root `npm ci` runs every workspace's `prepare`, which builds it, so + # the head build the diff probes exists after this step. + - name: Install dependencies and build + run: npm ci + + - name: Choose the base to compare against + id: base + env: + EVENT: ${{ github.event_name }} + PR_BASE: ${{ github.event.pull_request.base.sha }} + BEFORE: ${{ github.event.before }} + INPUT_BASE: ${{ inputs.compare_ref }} run: | - echo "" >> $GITHUB_STEP_SUMMARY - echo "---" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "ℹ️ **Interpreting Results**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "The Everything server is a reference implementation that exercises all MCP features." >> $GITHUB_STEP_SUMMARY - echo "Interface changes here may indicate:" >> $GITHUB_STEP_SUMMARY - echo "- New MCP protocol features being demonstrated" >> $GITHUB_STEP_SUMMARY - echo "- Updated tool schemas or descriptions" >> $GITHUB_STEP_SUMMARY - echo "- New resources, prompts, or capabilities" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Review changes to ensure they align with the intended protocol updates." >> $GITHUB_STEP_SUMMARY + case "$EVENT" in + # The checkout is the PR's merge commit, so its base tip is + # exactly what the PR changes. + pull_request) base="$PR_BASE" ;; + # The branch as it was before this push; a new branch has none. + push) + if [ -n "$BEFORE" ] && [ "$BEFORE" != 0000000000000000000000000000000000000000 ]; then + base="$BEFORE" + else + base=$(git rev-parse HEAD^) + fi ;; + release) base=$(git describe --tags --abbrev=0 HEAD^) ;; + workflow_dispatch) + if [ -n "$INPUT_BASE" ]; then + base="$INPUT_BASE" + else + base=$(git merge-base HEAD origin/v2/main) + fi ;; + *) echo "unexpected event: $EVENT" >&2; exit 1 ;; + esac + echo "Comparing against $base" + echo "ref=$base" >> "$GITHUB_OUTPUT" - # Post (or update) a sticky PR comment so reviewers can see the diff without - # navigating to the Actions tab. The same comment is updated on subsequent - # pushes via the `header` key. Skipped for PRs from forks where GITHUB_TOKEN - # is read-only — the report is still available in the workflow summary and - # as an uploaded artifact. - - name: Build PR comment body - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository - id: comment_body + - name: Diff the interface + env: + BASE: ${{ steps.base.outputs.ref }} + run: npm run interface-diff -- --base "$BASE" --report-dir mcp-diff-report + + - name: Write the job summary + if: always() run: | { - echo 'body<Full report' - echo '' - tail -n +5 conformance-report/CONFORMANCE_REPORT.md - echo '' - echo '' - else - echo '_Report file not found — see the workflow logs for details._' - fi - echo '' - echo "Updated by [\`${{ github.workflow }}\`](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) · commit \`${{ github.event.pull_request.head.sha }}\`" - echo 'MCP_DIFF_EOF' - } >> "$GITHUB_OUTPUT" + echo "" + echo "---" + echo "" + echo "ℹ️ **Interpreting Results**" + echo "" + echo "The Everything server is a reference implementation that exercises all MCP features." + echo "Interface changes here may indicate:" + echo "- New MCP protocol features being demonstrated" + echo "- Updated tool schemas or descriptions" + echo "- New resources, prompts, or capabilities" + echo "" + echo "Review changes to ensure they align with the intended protocol updates." + } >> "$GITHUB_STEP_SUMMARY" - - name: Post / update sticky PR comment - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository - uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 + - name: Upload the report + if: always() + uses: actions/upload-artifact@v7 + with: + name: mcp-diff-report + path: mcp-diff-report/ + if-no-files-found: ignore + + # Post (or update) a sticky PR comment so reviewers can see the diff without + # navigating to the Actions tab. One comment per PR, found again by its + # marker and edited on each push. This job checks nothing out and runs none + # of the PR's code; it only reads the report the `diff` job uploaded. + comment: + name: Post the diff on the PR + needs: diff + if: >- + always() && + github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository && + (needs.diff.result == 'success' || needs.diff.result == 'failure') + timeout-minutes: 5 + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Download the report + uses: actions/download-artifact@v8 + continue-on-error: true with: - header: everything-server-mcp-diff - message: ${{ steps.comment_body.outputs.body }} + name: mcp-diff-report + path: mcp-diff-report + + - name: Post / update sticky PR comment + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + PR: ${{ github.event.pull_request.number }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + marker='' + { + echo "$marker" + if [ -f mcp-diff-report/report.md ]; then + # A comment holds at most 65536 characters. + head -c 60000 mcp-diff-report/report.md + if [ "$(wc -c < mcp-diff-report/report.md)" -gt 60000 ]; then + echo "" + echo "_The report is truncated here; the full report is in the run's \`mcp-diff-report\` artifact._" + fi + else + echo "## \`everything\`: MCP interface diff" + echo "" + echo "❌ The diff job produced no report; see the run's log." + fi + echo "" + echo "Updated by [this run]($RUN_URL) for \`$HEAD_SHA\`." + } > body.md + id=$(gh api --paginate "repos/$REPO/issues/$PR/comments" \ + --jq ".[] | select(.user.login == \"github-actions[bot]\" and (.body | startswith(\"$marker\"))) | .id" | head -n 1) + if [ -n "$id" ]; then + gh api -X PATCH "repos/$REPO/issues/comments/$id" -F body=@body.md > /dev/null + else + gh api "repos/$REPO/issues/$PR/comments" -F body=@body.md > /dev/null + fi diff --git a/package-lock.json b/package-lock.json index d1390cede6..6359f597db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "@modelcontextprotocol/client": "^2.3.1", "eslint": "^10.11.0", "globals": "^17.12.0", + "mcp-server-diff": "3.0.0", "prettier": "3.8.4", "proper-lockfile": "^4.1.2", "semver": "^7.8.4", @@ -31,6 +32,50 @@ "yaml": "^2.9.0" } }, + "node_modules/@actions/core": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.1.tgz", + "integrity": "sha512-a6d/Nwahm9fliVGRhdhofo40HjHQasUPusmc7vBfyky+7Z+P2A1J68zyFVaNcEclc/Se+eO595oAr5nwEIoIUA==", + "dev": true, + "dependencies": { + "@actions/exec": "^3.0.0", + "@actions/http-client": "^4.0.0" + } + }, + "node_modules/@actions/exec": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-3.0.0.tgz", + "integrity": "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==", + "dev": true, + "dependencies": { + "@actions/io": "^3.0.2" + } + }, + "node_modules/@actions/http-client": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.1.tgz", + "integrity": "sha512-+Nvd1ImaOZBSoPbsUtEhv+1z99H12xzncCkz0a3RuehINE81FZSe2QTj3uvAPTcJX/SCzUQHQ0D1GrPMbrPitg==", + "dev": true, + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^6.23.0" + } + }, + "node_modules/@actions/http-client/node_modules/undici": { + "version": "6.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.0.tgz", + "integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==", + "dev": true, + "engines": { + "node": ">=18.17" + } + }, + "node_modules/@actions/io": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-3.0.2.tgz", + "integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==", + "dev": true + }, "node_modules/@babel/helper-string-parser": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", @@ -3805,6 +3850,36 @@ "node": ">= 0.4" } }, + "node_modules/mcp-server-diff": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mcp-server-diff/-/mcp-server-diff-3.0.0.tgz", + "integrity": "sha512-vtbf+ZSfRFMZQipzIpQDP5l35qMhW/CtemsERMr7DjU5Z+uY6ZtL8gHBAamwZ2sS3VvsWtVqMg1Hk91HbavZrw==", + "dev": true, + "dependencies": { + "@actions/core": "^3.0.1", + "@actions/exec": "^3.0.0", + "@actions/io": "^3.0.2", + "@modelcontextprotocol/sdk": "^1.13.2", + "diff": "^9.0.0", + "undici": "^8.4.1", + "zod": "^4.4.3" + }, + "bin": { + "mcp-server-diff": "dist/cli/index.js" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/mcp-server-diff/node_modules/diff": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/media-typer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", @@ -4972,6 +5047,15 @@ "license": "0BSD", "optional": true }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5054,6 +5138,15 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/undici": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz", + "integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==", + "dev": true, + "engines": { + "node": ">=22.19.0" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", diff --git a/package.json b/package.json index a1d225897a..49ed4ba99e 100644 --- a/package.json +++ b/package.json @@ -43,10 +43,11 @@ "verify:action-pins": "node scripts/verify-action-pins.mjs", "verify:dco": "node scripts/verify-dco.mjs", "smoke": "node scripts/smoke-servers.mjs", + "interface-diff": "node scripts/interface-diff.mjs", "pack:verify": "node scripts/pack-and-verify.mjs", "release:notes": "node scripts/release-notes.mjs", "local:gate": "node scripts/gate-lease.mjs npm run local:gate:stages", - "local:gate:stages": "npm run verify:install-fresh && npm run verify:dco && npm run validate && npm run coverage && npm run validate:py && npm run coverage:py && npm run verify:skills:cli && npm run smoke" + "local:gate:stages": "npm run verify:install-fresh && npm run verify:dco && npm run validate && npm run coverage && npm run validate:py && npm run coverage:py && npm run verify:skills:cli && npm run smoke && npm run interface-diff" }, "dependencies": { "@modelcontextprotocol/server-everything": "*", @@ -61,6 +62,7 @@ "@modelcontextprotocol/client": "^2.3.1", "eslint": "^10.11.0", "globals": "^17.12.0", + "mcp-server-diff": "3.0.0", "prettier": "3.8.4", "proper-lockfile": "^4.1.2", "semver": "^7.8.4", diff --git a/scripts/interface-diff.mjs b/scripts/interface-diff.mjs new file mode 100644 index 0000000000..3b46b27bc0 --- /dev/null +++ b/scripts/interface-diff.mjs @@ -0,0 +1,345 @@ +#!/usr/bin/env node +// The `everything` server's MCP interface diff (#4860), adapted from +// @SamMorrowDrums's workflow in #3260. +// +// It snapshots the public interface of two builds of `everything` (the server +// capabilities from the handshake, and its tools, prompts, resources and +// resource templates) with `mcp-server-diff`, and reports what changed between +// them: the build in this checkout (the head) against the same server built +// from a base commit. The spec refactor (#4857) leans on it: an SDK migration +// that is meant to be transparent proves it with an empty diff, and each new +// 2026-07-28 feature shows up as a reviewable one. +// +// Why a script, and not the `mcp-server-diff` GitHub Action #3260 used: +// +// - `local:gate` must run every check CI runs, and +// `scripts/lib/workflow-gate.test.mjs` derives that from the npm scripts a +// workflow invokes. A check behind an action is invisible to it; a check +// behind `npm run interface-diff` is enforced by it. CI +// (`everything-mcp-diff.yml`) and the gate run this same file. +// - The tool comes in as an exact-pinned root devDependency, so it is held +// by `package-lock.json`'s integrity hash, the way every other npm +// dependency here is, and no third-party action runs in CI at all. +// - The action picks the base itself, as the merge-base with `origin/main`. +// PRs here target `v2/main`, so that base is wrong; the caller names it. +// +// The base is built from a plain export of the base commit's tree (no git +// worktree is registered) under `node_modules/.cache/`, which git, Prettier and +// ESLint all ignore, and removed afterwards. It sits inside the checkout rather +// than under the system temp directory because `mcp-server-diff` splits a +// server's start command on whitespace, and a relative path from the checkout +// cannot contain any, where a temp directory under a Windows profile can. +// +// The head is NOT built here: inside `local:gate` the `validate` stage has +// already built it, and CI's root `npm ci` builds it. A missing build is an +// error, as it is for the boot smoke. +// +// Outcome: +// - unchanged / changed: exit 0. A changed interface is information for the +// reviewer, not a failure (#3260 ran with `fail_on_diff: false` too). +// - error: exit 1. A server that cannot be probed, or a base that does not +// build, is a broken check, so it fails the gate and the CI job. +// +// Usage: +// npm run interface-diff # base: merge-base with origin/v2/main +// npm run interface-diff -- --base # any commit, tag or branch +// npm run interface-diff -- --report-dir # also write report.md and result.json + +import { spawnSync } from "node:child_process"; +import { + existsSync, + mkdirSync, + mkdtempSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { createRequire } from "node:module"; +import path from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const repoRoot = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + "..", +); + +/** The server this diff covers. Start with `everything` only (#4860). */ +export const SERVER_DIR = "src/everything"; + +/** The entry point a client launches, relative to a checkout's root. */ +const ENTRY = `${SERVER_DIR}/dist/index.js`; + +/** The base when none is named: where this branch left the develop line. */ +export const DEFAULT_BASE_BRANCH = "origin/v2/main"; + +/** + * Parse the command line. + * + * @param {string[]} argv + * @returns {{ base: string | null, reportDir: string | null }} + */ +export function parseArgs(argv) { + const opts = { base: null, reportDir: null }; + for (let i = 0; i < argv.length; i += 1) { + const arg = argv[i]; + const [flag, inline] = arg.startsWith("--") ? arg.split(/=(.*)/s) : [arg]; + const value = () => { + const v = inline ?? argv[++i]; + if (v === undefined || v === "") throw new Error(`${flag} needs a value`); + return v; + }; + if (flag === "--base") opts.base = value(); + else if (flag === "--report-dir") opts.reportDir = value(); + else throw new Error(`unknown argument: ${arg}`); + } + return opts; +} + +/** + * Turn `mcp-server-diff`'s JSON output into this diff's verdict. + * + * The CLI exits 1 both when the interfaces differ and when a probe fails, so + * its exit code cannot tell a change from a broken check; the JSON can. + * + * @param {string} stdout what `mcp-server-diff -o json -q` printed + * @returns {{ status: "unchanged" | "changed" | "error", error?: string, + * diffs: { endpoint: string, diff: string }[], + * baseCounts?: object, headCounts?: object }} + */ +export function classify(stdout) { + let parsed; + try { + parsed = JSON.parse(stdout); + } catch { + return { + status: "error", + error: `mcp-server-diff printed no JSON report:\n${stdout.trim().slice(0, 2000)}`, + diffs: [], + }; + } + const result = parsed?.results?.[0]; + if (!result || typeof result !== "object") + return { + status: "error", + error: "mcp-server-diff reported no comparison", + diffs: [], + }; + const diffs = Array.isArray(result.diffs) ? result.diffs : []; + if (result.error) + return { status: "error", error: String(result.error), diffs }; + return { + status: result.hasDifferences ? "changed" : "unchanged", + diffs, + baseCounts: result.baseCounts, + headCounts: result.targetCounts, + }; +} + +/** A Markdown code fence longer than any run of backticks in `text`. */ +export function fenceFor(text) { + const longest = Math.max( + 0, + ...[...text.matchAll(/`+/g)].map((m) => m[0].length), + ); + return "`".repeat(Math.max(3, longest + 1)); +} + +/** + * The human-readable report: the verdict first, then the counts, then each + * part of the interface that changed as a diff. + * + * @param {ReturnType} verdict + * @param {{ base: string, head: string }} labels + */ +export function renderReport(verdict, { base, head }) { + const lines = ["## `everything`: MCP interface diff", ""]; + lines.push(`Base: ${base} `, `Head: ${head}`, ""); + if (verdict.status === "unchanged") + lines.push("✅ **No interface changes detected.**"); + else if (verdict.status === "changed") + lines.push( + `⚠️ **Interface changes detected** in ${verdict.diffs.length} part(s) of the interface. Review them below to confirm they are intended.`, + ); + else lines.push("❌ **The interface diff could not run.**"); + lines.push(""); + + if (verdict.status === "error") { + const fence = fenceFor(verdict.error ?? ""); + lines.push(fence, verdict.error ?? "", fence, ""); + return lines.join("\n"); + } + + const row = (name, c = {}) => + `| ${name} | ${c.tools ?? "?"} | ${c.prompts ?? "?"} | ${c.resources ?? "?"} | ${c.resourceTemplates ?? "?"} |`; + lines.push( + "| | Tools | Prompts | Resources | Resource templates |", + "| --- | --- | --- | --- | --- |", + row("Base", verdict.baseCounts), + row("Head", verdict.headCounts), + "", + ); + + for (const { endpoint, diff } of verdict.diffs) { + const fence = fenceFor(diff); + lines.push(`### ${endpoint}`, "", `${fence}diff`, diff, fence, ""); + } + return lines.join("\n"); +} + +/** Run a command to completion; throw with its output when it fails. */ +function run(command, args, cwd, env) { + const res = spawnSync(command, args, { + cwd, + env: env ?? process.env, + encoding: "utf8", + maxBuffer: 64 * 1024 * 1024, + shell: process.platform === "win32", + }); + if (res.error) throw res.error; + if (res.status !== 0) + throw new Error( + `\`${command} ${args.join(" ")}\` exited ${res.status}:\n${(res.stderr || res.stdout).trim().split("\n").slice(-25).join("\n")}`, + ); + return res.stdout; +} + +const git = (...args) => run("git", args, repoRoot).trim(); + +/** The commit to compare against, as a full SHA. */ +function resolveBase(base) { + if (base) return git("rev-parse", "--verify", `${base}^{commit}`); + try { + git("rev-parse", "--verify", DEFAULT_BASE_BRANCH); + } catch { + throw new Error( + `${DEFAULT_BASE_BRANCH} is not available to compare against. Run \`git fetch origin v2/main\`, or name a base with --base .`, + ); + } + return git("merge-base", "HEAD", DEFAULT_BASE_BRANCH); +} + +/** + * Build `everything` as it was at `sha` into `dir`: export the commit's tree + * through a throwaway index (no worktree registered, the real index + * untouched), install that workspace's locked dependencies, build it. + */ +function buildBase(sha, dir) { + const index = `${dir}.index`; + const env = { ...process.env, GIT_INDEX_FILE: index }; + try { + run("git", ["read-tree", sha], repoRoot, env); + run( + "git", + ["checkout-index", "--all", `--prefix=${dir}${path.sep}`], + repoRoot, + env, + ); + } finally { + rmSync(index, { force: true }); + } + if (!existsSync(path.join(dir, SERVER_DIR, "package.json"))) + throw new Error(`${SERVER_DIR} does not exist at the base ${sha}`); + run( + "npm", + [ + "ci", + "--workspace", + SERVER_DIR, + "--ignore-scripts", + "--no-audit", + "--no-fund", + "--prefer-offline", + ], + dir, + ); + run("npm", ["run", "build", "--workspace", SERVER_DIR], dir); +} + +/** Where the pinned `mcp-server-diff` CLI lives. */ +function cliPath() { + const require = createRequire(import.meta.url); + const manifest = require.resolve("mcp-server-diff/package.json"); + const { bin } = require("mcp-server-diff/package.json"); + const rel = typeof bin === "string" ? bin : bin["mcp-server-diff"]; + return path.join(path.dirname(manifest), rel); +} + +/** A short, readable label for a commit. */ +function describe(sha) { + const subject = git("log", "-1", "--format=%s", sha); + return `\`${sha.slice(0, 12)}\` (${subject.replaceAll("`", "'")})`; +} + +export async function main(argv = process.argv.slice(2)) { + let opts; + try { + opts = parseArgs(argv); + } catch (err) { + console.error(`interface-diff: ${err.message}`); + return 2; + } + + let verdict; + let labels = { base: "(unresolved)", head: "(unresolved)" }; + const cacheRoot = path.join(repoRoot, "node_modules", ".cache"); + let work; + try { + if (!existsSync(path.join(repoRoot, ENTRY))) + throw new Error( + `${ENTRY} does not exist — build first (npm run build -w ${SERVER_DIR}).`, + ); + const sha = resolveBase(opts.base); + const headSha = git("rev-parse", "HEAD"); + labels = { + base: describe(sha), + head: `the build in this checkout, at ${describe(headSha)}`, + }; + console.error(`interface-diff: building the base ${sha.slice(0, 12)} …`); + + mkdirSync(cacheRoot, { recursive: true }); + work = mkdtempSync(path.join(cacheRoot, "servers-interface-diff-")); + buildBase(sha, work); + + // Both start commands are relative to the checkout, so they hold no + // whitespace for mcp-server-diff to split on (see the header). + const baseEntry = path + .relative(repoRoot, path.join(work, ENTRY)) + .split(path.sep) + .join("/"); + console.error("interface-diff: probing the base and the head …"); + const res = spawnSync( + process.execPath, + [ + cliPath(), + "--base", + `node ${baseEntry} stdio`, + "--target", + `node ${ENTRY} stdio`, + "--output", + "json", + "--quiet", + ], + { cwd: repoRoot, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }, + ); + if (res.error) throw res.error; + verdict = classify(res.stdout); + } catch (err) { + verdict = { status: "error", error: err.message, diffs: [] }; + } finally { + if (work) rmSync(work, { recursive: true, force: true }); + } + + const report = renderReport(verdict, labels); + console.log(report); + if (opts.reportDir) { + mkdirSync(opts.reportDir, { recursive: true }); + writeFileSync(path.join(opts.reportDir, "report.md"), `${report}\n`); + writeFileSync( + path.join(opts.reportDir, "result.json"), + `${JSON.stringify({ status: verdict.status, diffCount: verdict.diffs.length }, null, 2)}\n`, + ); + } + return verdict.status === "error" ? 1 : 0; +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) + process.exitCode = await main(); diff --git a/scripts/interface-diff.test.mjs b/scripts/interface-diff.test.mjs new file mode 100644 index 0000000000..e30008c34e --- /dev/null +++ b/scripts/interface-diff.test.mjs @@ -0,0 +1,127 @@ +// Tests for the `everything` interface diff (#4860). The pure halves are +// exercised here: the argument parser, the verdict drawn from +// `mcp-server-diff`'s JSON (the one thing that decides whether the gate and +// the CI job fail), and the report. Building a base and probing two servers is +// what `npm run interface-diff` itself does as a gate stage, so it is not +// repeated here. Run via `npm run test:scripts`. + +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { + classify, + fenceFor, + main, + parseArgs, + renderReport, +} from "./interface-diff.mjs"; + +const counts = { tools: 13, prompts: 4, resources: 7, resourceTemplates: 2 }; +const cliJson = (result) => + JSON.stringify({ timestamp: "t", results: [result], summary: {} }); + +test("parseArgs reads both flags, in either spelling", () => { + assert.deepEqual(parseArgs([]), { base: null, reportDir: null }); + assert.deepEqual(parseArgs(["--base", "abc", "--report-dir", "out"]), { + base: "abc", + reportDir: "out", + }); + assert.deepEqual(parseArgs(["--base=v1.0.0", "--report-dir=a=b"]), { + base: "v1.0.0", + reportDir: "a=b", + }); +}); + +test("parseArgs refuses an unknown flag or a missing value", () => { + assert.throws(() => parseArgs(["--fail-on-diff"]), /unknown argument/); + assert.throws(() => parseArgs(["--base"]), /--base needs a value/); + assert.throws(() => parseArgs(["--base="]), /--base needs a value/); +}); + +test("main exits 2 on a bad command line, before building anything", async () => { + assert.equal(await main(["--nope"]), 2); +}); + +test("an identical interface is unchanged", () => { + const v = classify( + cliJson({ + hasDifferences: false, + baseCounts: counts, + targetCounts: counts, + diffs: [], + }), + ); + assert.equal(v.status, "unchanged"); + assert.deepEqual(v.headCounts, counts); +}); + +test("a difference is a change, not an error", () => { + const diffs = [{ endpoint: "tools", diff: "- a\n+ b" }]; + const v = classify( + cliJson({ + hasDifferences: true, + baseCounts: counts, + targetCounts: counts, + diffs, + }), + ); + assert.equal(v.status, "changed"); + assert.deepEqual(v.diffs, diffs); +}); + +test("a probe failure is an error, though the CLI reports it as a difference", () => { + // mcp-server-diff sets hasDifferences on a failed probe too, and exits 1 + // for both; only the `error` field tells them apart. + const v = classify( + cliJson({ + hasDifferences: true, + error: "Target probe failed: boom", + diffs: [{ endpoint: "error", diff: "Target probe failed: boom" }], + }), + ); + assert.equal(v.status, "error"); + assert.match(v.error, /boom/); +}); + +test("output that is not the JSON report is an error, never a pass", () => { + assert.equal(classify("").status, "error"); + assert.equal(classify("Fatal error: x").status, "error"); + assert.equal(classify("{}").status, "error"); + assert.equal(classify(JSON.stringify({ results: [] })).status, "error"); +}); + +test("fenceFor outruns any backticks in the content", () => { + assert.equal(fenceFor("plain"), "```"); + assert.equal(fenceFor("a ``` b"), "````"); + assert.equal(fenceFor("`````"), "``````"); +}); + +test("the report leads with the verdict and shows each changed part", () => { + const labels = { base: "`base`", head: "`head`" }; + assert.match( + renderReport( + { + status: "unchanged", + diffs: [], + baseCounts: counts, + headCounts: counts, + }, + labels, + ), + /No interface changes detected[\s\S]*\| Head \| 13 \| 4 \| 7 \| 2 \|/, + ); + const changed = renderReport( + { + status: "changed", + diffs: [{ endpoint: "tools", diff: "- x ``` y" }], + baseCounts: counts, + headCounts: counts, + }, + labels, + ); + assert.match(changed, /Interface changes detected/); + assert.match(changed, /### tools\n\n````diff\n- x ``` y\n````/); + assert.match( + renderReport({ status: "error", error: "boom", diffs: [] }, labels), + /could not run[\s\S]*boom/, + ); +}); diff --git a/scripts/lib/workflow-gate.test.mjs b/scripts/lib/workflow-gate.test.mjs index 6dee0ca4fd..c08ae56186 100644 --- a/scripts/lib/workflow-gate.test.mjs +++ b/scripts/lib/workflow-gate.test.mjs @@ -392,6 +392,7 @@ describe("the gate's name", () => { "coverage:py", "verify:skills:cli", "smoke", + "interface-diff", ]) { it(`runs \`${stage}\``, () => { assert.ok( From dde6224267e814de530f67ee6def459329f41c5a Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sun, 4 Oct 2026 02:13:33 -0400 Subject: [PATCH 20/65] docs: record the interface diff as a gate stage (#4860) Add `interface-diff` to the stage table in docs/quality-gate.md, to AGENTS.md's Before pushing and project tree, and a diagnosis section to the pre-push-gate skill. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .claude/skills/pre-push-gate/SKILL.md | 25 +++++++++++++++++++++++++ AGENTS.md | 17 +++++++++-------- docs/quality-gate.md | 18 +++++++++++++++--- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/.claude/skills/pre-push-gate/SKILL.md b/.claude/skills/pre-push-gate/SKILL.md index c7198ad3a9..ddb54b47ca 100644 --- a/.claude/skills/pre-push-gate/SKILL.md +++ b/.claude/skills/pre-push-gate/SKILL.md @@ -273,6 +273,31 @@ of the server's stderr. Re-run a subset with - **A new server fails `test:scripts`** with "every server directory in the repo has a smoke entry": add it to `SERVERS` in `scripts/smoke-servers.mjs`. +### `interface-diff` + +The last stage diffs the `everything` server's MCP interface (capabilities, +tools, prompts, resources, resource templates) in this checkout's build +against the same server built from the merge-base with `origin/v2/main`, and +prints the report. **A changed interface does not fail it**: the report is +what the PR's sticky comment will show, so read it, and if a change is not +one you meant, that is the finding. It fails only when the diff cannot run, +with `❌ The interface diff could not run` and the reason: + +- **"origin/v2/main is not available"**: `git fetch origin v2/main`. +- **"src/everything/dist/index.js does not exist"**: you ran it on its own + before building: `npm run build -w src/everything`. +- **An `npm ci` or `npm run build` failure**: the _base_ did not install or + build. Offline with a cold npm cache is the usual cause; otherwise name a + base that builds with `npm run interface-diff -- --base `. +- **"Base server probe failed" / "Target probe failed"**: a server did not + answer the probe. A target failure is this checkout's server and is the + real defect; the smoke stage before it usually fails first. + +Re-run it on its own with `npm run interface-diff` (about ten seconds). What +it compares against in CI, and why it is a script rather than an action, is +in [`docs/quality-gate.md`](../../../docs/quality-gate.md) and the header of +`scripts/interface-diff.mjs`. + ## Waiting on the lease A gate that starts with diff --git a/AGENTS.md b/AGENTS.md index bba09c93db..7b4abbd327 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -52,7 +52,7 @@ servers/ │ └── time/ Py mcp-server-time PyPI Time and timezone conversion ├── .claude/skills/ On-demand procedures (see the Skills index above) ├── .changeset/ Pending changesets for the TypeScript servers, and the changesets config -├── scripts/ The pre-push gate (gate-lease, smoke-servers, validate-py, coverage-py), its guards (verify-*), +├── scripts/ The pre-push gate (gate-lease, smoke-servers, validate-py, coverage-py, interface-diff), its guards (verify-*), │ the skills tooling, release tooling (npm-publish-guard, prepare-python-release, │ pack-and-verify, release-manifest, release-notes), and the issue-filing sweeps │ (dependency-refresh, dependabot-alerts, sdk-watch) @@ -61,8 +61,8 @@ servers/ │ overview of how the rules, skills, gates and sweeps fit together ├── .github/workflows/ typescript.yml, python.yml (per-package CI), release.yml (publishes on a GitHub Release), │ version-packages.yml (the changesets PR), prepare-python-release.yml (the CalVer PR), -│ claude.yml (@claude mentions), and the scheduled sweeps that file issues: -│ dependency-refresh.yml, dependabot-alerts.yml, sdk-watch.yml +│ claude.yml (@claude mentions), everything-mcp-diff.yml (everything's interface diff on PRs), +│ and the scheduled sweeps that file issues: dependency-refresh.yml, dependabot-alerts.yml, sdk-watch.yml ├── .github/ISSUE_TEMPLATE/ Bug and feature issue forms; config.yml routes security and new servers away ├── .github/pull_request_template.md The "issues, not PRs" banner that turns outside PRs away ├── RELEASING.md How packages are versioned and published, and how to recover a failed publish @@ -129,14 +129,15 @@ workspace's format check, lint, typecheck, build and tests), `coverage` (each TypeScript workspace's per-file coverage gate), `validate:py` (each Python server's locked sync, `ruff check`, `ruff format --check`, pyright, pytest and build), `coverage:py` (each Python server's per-file -coverage gate), `verify:skills:cli`, and `smoke` (every server -booted over each transport it implements). The stage-by-stage reference, and -what CI runs where, is [`docs/quality-gate.md`](./docs/quality-gate.md); +coverage gate), `verify:skills:cli`, `smoke` (every server booted over each +transport it implements), and `interface-diff` (the `everything` server's MCP +interface diffed against the base it branched from). The stage-by-stage +reference, and what CI runs where, is [`docs/quality-gate.md`](./docs/quality-gate.md); diagnosing a red stage is the `pre-push-gate` skill. - **`npm run validate` and the per-package commands above are the inner loop, - not a substitute.** They skip the other language, the pinned skills validator - and the boot smoke. + not a substitute.** They skip the other language, the pinned skills validator, + the boot smoke and the interface diff. - **Format before committing**: `npm run format` at the root (TypeScript), and `uv run --frozen ruff format .` in a Python server you changed. The gate only checks formatting; it never rewrites files. diff --git a/docs/quality-gate.md b/docs/quality-gate.md index 075b72de24..7c197a5b05 100644 --- a/docs/quality-gate.md +++ b/docs/quality-gate.md @@ -13,7 +13,7 @@ out; everything else points here. | --- | --- | --- | --- | | Inner loop | `npm run validate -w src/`, `npm run validate:py -- ` | Your machine | While iterating on one server | | Pre-push gate | `npm run local:gate` | Your machine, under a lease | Before every push | -| CI | `.github/workflows/typescript.yml`, `python.yml`; `dco.yml` | GitHub, on every push and pull request; `dco.yml` on pull requests only | Before merge | +| CI | `.github/workflows/typescript.yml`, `python.yml`, `everything-mcp-diff.yml`; `dco.yml` | GitHub, on every push and pull request (`everything-mcp-diff.yml`: when `everything` or what builds it changes); `dco.yml` on pull requests only | Before merge | **`npm run local:gate` runs every check CI runs.** CI splits the same checks into parallel jobs on a fresh install; the gate runs them in sequence on your @@ -49,6 +49,7 @@ in this order, and the first failure stops the run. | 7 | `coverage:py` | Per Python server: `uv sync --locked`, then `uv run --frozen pytest --cov --cov-report=term-missing --cov-report=json`; then **every file** in `coverage.json` must reach **90% on lines and 90% on branches** | `python.yml` → **Coverage \** (one leg each) | | 8 | `verify:skills:cli` | `claude plugin validate` on `.claude/skills`, at the pinned CLI version | `typescript.yml` → **Root guards** | | 9 | `smoke` | Every server boots over each transport it implements and answers one tool call | `typescript.yml` → **Boot smoke** | +| 10 | `interface-diff` | The `everything` server's MCP interface (capabilities, tools, prompts, resources, resource templates) in this checkout's build, diffed against the same server built from the base. It fails only when the diff cannot run; a changed interface is reported, not failed | `everything-mcp-diff.yml` → **Diff Everything Server Interface** (and the PR comment) | Notes on the stages: @@ -91,8 +92,9 @@ Notes on the stages: when a server's environment is missing or behind its lockfile, since `uv sync --locked` then downloads packages (and the pinned interpreter, if `uv` does not have it); `coverage:py` begins with the same sync, which is a - no-op once `validate:py` has run. With warm caches those are the only stages - that can fail offline. + no-op once `validate:py` has run; and `interface-diff` needs it for the + base's `npm ci`. With warm caches those are the only stages that can fail + offline. - **`smoke` launches what a user launches**: the built `dist/index.js` for a TypeScript server, the console script through `uv run --no-sync` for a Python one. stdio for all seven; HTTP+SSE and Streamable HTTP as well for @@ -102,6 +104,16 @@ Notes on the stages: than the default 3001. It runs after `validate` and `validate:py` because it needs the build and each Python server's synced environment; it creates neither. +- **`interface-diff` compares against the merge-base with `origin/v2/main`** + locally (`-- --base ` names another), and against the PR's base, the + commit before the push, or the previous release's tag in CI. It builds the + base from an export of that commit under `node_modules/.cache/` and removes + it afterwards; the head is the build `validate` made. It drives the + `mcp-server-diff` CLI, an exact-pinned root devDependency, which probes with + the 2026-07-28 `server/discover` first and falls back to the `initialize` + handshake, so it covers a server in either era. Its `npm ci` of the base + needs the network when the npm cache is cold, and it needs + `origin/v2/main` fetched. Only `everything` is diffed today (#4860). - `python.yml` also has a **Build \** job per server that re-runs pyright and `uv build` and uploads the built distribution. It checks nothing stage 6 does not. From b35e29de98458b5ca86f0d926b6e899401bb4ab3 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sun, 4 Oct 2026 02:14:23 -0400 Subject: [PATCH 21/65] ci(everything): diff a Release against the previous Release (#4860) `git describe` finds the nearest ancestor tag, but the date-stamped releases tagged commits that never reached main, so from main it lands on typescript-servers-0.6.2 (2024). Take the previous published Release's tag from the Releases list instead. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .github/workflows/everything-mcp-diff.yml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/everything-mcp-diff.yml b/.github/workflows/everything-mcp-diff.yml index 30f556c8e6..27107e0026 100644 --- a/.github/workflows/everything-mcp-diff.yml +++ b/.github/workflows/everything-mcp-diff.yml @@ -48,7 +48,9 @@ on: - "scripts/interface-diff.mjs" - ".github/workflows/everything-mcp-diff.yml" # A published Release (a milestone, tagged vX.Y.Z on main; see RELEASING.md) - # surfaces the cumulative interface delta since the previous release's tag. + # surfaces the cumulative interface delta since the previous Release. This + # replaces #3260's `typescript-servers-*` tag trigger: those tags stopped in + # 2024, and no release since has used them. release: types: [published] # Manual trigger for comparing any two refs (commits, tags, branches) @@ -84,7 +86,7 @@ jobs: uses: actions/checkout@v6 with: # The base is built from history, so all of it is needed, tags - # included (the release base is the previous tag). + # included (the release base is the previous Release's tag). fetch-depth: 0 ref: ${{ inputs.target_ref || github.ref }} persist-credentials: false @@ -106,6 +108,9 @@ jobs: PR_BASE: ${{ github.event.pull_request.base.sha }} BEFORE: ${{ github.event.before }} INPUT_BASE: ${{ inputs.compare_ref }} + TAG: ${{ github.event.release.tag_name }} + REPO: ${{ github.repository }} + GH_TOKEN: ${{ github.token }} run: | case "$EVENT" in # The checkout is the PR's merge commit, so its base tip is @@ -118,7 +123,14 @@ jobs: else base=$(git rev-parse HEAD^) fi ;; - release) base=$(git describe --tags --abbrev=0 HEAD^) ;; + # The previous published Release's tag. Not `git describe`: the + # date-stamped releases tagged commits that never reached main, so + # the nearest ancestor tag is years older than the last release. + release) + base=$(gh release list --repo "$REPO" --exclude-drafts --exclude-pre-releases --limit 50 \ + --json tagName,publishedAt \ + --jq "map(select(.tagName != \"$TAG\")) | sort_by(.publishedAt) | last | .tagName // empty") + if [ -z "$base" ]; then echo "no earlier Release to compare against" >&2; exit 1; fi ;; workflow_dispatch) if [ -n "$INPUT_BASE" ]; then base="$INPUT_BASE" From f242458df7869e57b15f001c558ea277a0426885 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sun, 4 Oct 2026 03:17:42 -0400 Subject: [PATCH 22/65] fix(interface-diff): check out the event's SHA; report the CLI's stderr (#4860) Copilot round 1 on #4975: - Check out github.sha rather than github.ref, so a push run diffs the commit it was triggered for against that event's `before`, not a branch tip a later push moved. - When mcp-server-diff prints no JSON report, include its stderr (where a fatal error goes) in the error, so the reason is not blank. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .github/workflows/everything-mcp-diff.yml | 4 +++- scripts/interface-diff.mjs | 12 +++++++++--- scripts/interface-diff.test.mjs | 6 ++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/everything-mcp-diff.yml b/.github/workflows/everything-mcp-diff.yml index 27107e0026..43fae86a20 100644 --- a/.github/workflows/everything-mcp-diff.yml +++ b/.github/workflows/everything-mcp-diff.yml @@ -88,7 +88,9 @@ jobs: # The base is built from history, so all of it is needed, tags # included (the release base is the previous Release's tag). fetch-depth: 0 - ref: ${{ inputs.target_ref || github.ref }} + # The event's own commit (the merge commit for a PR), not a branch + # ref that a later push could move before this step runs. + ref: ${{ inputs.target_ref || github.sha }} persist-credentials: false - uses: actions/setup-node@v7 diff --git a/scripts/interface-diff.mjs b/scripts/interface-diff.mjs index 3b46b27bc0..ce4c9ff63c 100644 --- a/scripts/interface-diff.mjs +++ b/scripts/interface-diff.mjs @@ -101,18 +101,24 @@ export function parseArgs(argv) { * its exit code cannot tell a change from a broken check; the JSON can. * * @param {string} stdout what `mcp-server-diff -o json -q` printed + * @param {string} [stderr] what it printed to stderr: a fatal error before the + * report goes there, so it is the reason when no report came out * @returns {{ status: "unchanged" | "changed" | "error", error?: string, * diffs: { endpoint: string, diff: string }[], * baseCounts?: object, headCounts?: object }} */ -export function classify(stdout) { +export function classify(stdout, stderr = "") { let parsed; try { parsed = JSON.parse(stdout); } catch { return { status: "error", - error: `mcp-server-diff printed no JSON report:\n${stdout.trim().slice(0, 2000)}`, + error: `mcp-server-diff printed no JSON report:\n${[stderr, stdout] + .map((s) => s.trim()) + .filter(Boolean) + .join("\n") + .slice(0, 2000)}`, diffs: [], }; } @@ -321,7 +327,7 @@ export async function main(argv = process.argv.slice(2)) { { cwd: repoRoot, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }, ); if (res.error) throw res.error; - verdict = classify(res.stdout); + verdict = classify(res.stdout, res.stderr); } catch (err) { verdict = { status: "error", error: err.message, diffs: [] }; } finally { diff --git a/scripts/interface-diff.test.mjs b/scripts/interface-diff.test.mjs index e30008c34e..0d36cbffbf 100644 --- a/scripts/interface-diff.test.mjs +++ b/scripts/interface-diff.test.mjs @@ -89,6 +89,12 @@ test("output that is not the JSON report is an error, never a pass", () => { assert.equal(classify(JSON.stringify({ results: [] })).status, "error"); }); +test("a CLI crash before the report carries its stderr as the reason", () => { + const v = classify("", "Fatal error: Error: spawn node ENOENT"); + assert.equal(v.status, "error"); + assert.match(v.error, /no JSON report[\s\S]*spawn node ENOENT/); +}); + test("fenceFor outruns any backticks in the content", () => { assert.equal(fenceFor("plain"), "```"); assert.equal(fenceFor("a ``` b"), "````"); From 0c2a61d62bd7206d9f43a4a2b94bf55cbed2c4b9 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sun, 4 Oct 2026 03:25:44 -0400 Subject: [PATCH 23/65] fix(interface-diff): diff a Release against its own predecessor (#4860) Copilot round 2 on #4975: taking the latest Release other than the current one picks a newer Release when an older one's run is re-run. Keep only Releases published before this event's Release. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .github/workflows/everything-mcp-diff.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/everything-mcp-diff.yml b/.github/workflows/everything-mcp-diff.yml index 43fae86a20..adbc391f65 100644 --- a/.github/workflows/everything-mcp-diff.yml +++ b/.github/workflows/everything-mcp-diff.yml @@ -111,6 +111,7 @@ jobs: BEFORE: ${{ github.event.before }} INPUT_BASE: ${{ inputs.compare_ref }} TAG: ${{ github.event.release.tag_name }} + PUBLISHED: ${{ github.event.release.published_at }} REPO: ${{ github.repository }} GH_TOKEN: ${{ github.token }} run: | @@ -125,13 +126,15 @@ jobs: else base=$(git rev-parse HEAD^) fi ;; - # The previous published Release's tag. Not `git describe`: the - # date-stamped releases tagged commits that never reached main, so - # the nearest ancestor tag is years older than the last release. + # The Release published just before this one (so a re-run of an + # older Release still compares against ITS predecessor). Not + # `git describe`: the date-stamped releases tagged commits that + # never reached main, so the nearest ancestor tag is years older + # than the last release. release) base=$(gh release list --repo "$REPO" --exclude-drafts --exclude-pre-releases --limit 50 \ --json tagName,publishedAt \ - --jq "map(select(.tagName != \"$TAG\")) | sort_by(.publishedAt) | last | .tagName // empty") + --jq "map(select(.tagName != \"$TAG\" and .publishedAt < \"$PUBLISHED\")) | sort_by(.publishedAt) | last | .tagName // empty") if [ -z "$base" ]; then echo "no earlier Release to compare against" >&2; exit 1; fi ;; workflow_dispatch) if [ -n "$INPUT_BASE" ]; then From a793ed4d99ab997472c5a969f797791581028d59 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sun, 4 Oct 2026 03:34:27 -0400 Subject: [PATCH 24/65] fix(interface-diff): Dependabot, root tsconfig, Windows quoting (#4860) Copilot round 3 on #4975 ("previously missed"): - Skip the comment job on Dependabot PRs, which are same-repo but get a read-only token; they keep the summary and artifact like fork PRs. - Trigger on the root tsconfig.json, which everything's tsconfig extends. - Spawn only npm through a shell on Windows, with its arguments quoted by winShellArgs; git needs no shell. - Silence the expected usage error in the script test. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .github/workflows/everything-mcp-diff.yml | 5 +++++ scripts/interface-diff.mjs | 12 +++++++++--- scripts/interface-diff.test.mjs | 5 ++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/everything-mcp-diff.yml b/.github/workflows/everything-mcp-diff.yml index adbc391f65..8add0835c9 100644 --- a/.github/workflows/everything-mcp-diff.yml +++ b/.github/workflows/everything-mcp-diff.yml @@ -36,6 +36,7 @@ on: - "src/everything/**" - "package.json" - "package-lock.json" + - "tsconfig.json" - "scripts/interface-diff.mjs" - ".github/workflows/everything-mcp-diff.yml" # No branch filter: PRs to v2/main and to main both run, and so does a PR @@ -45,6 +46,7 @@ on: - "src/everything/**" - "package.json" - "package-lock.json" + - "tsconfig.json" - "scripts/interface-diff.mjs" - ".github/workflows/everything-mcp-diff.yml" # A published Release (a milestone, tagged vX.Y.Z on main; see RELEASING.md) @@ -187,6 +189,8 @@ jobs: # navigating to the Actions tab. One comment per PR, found again by its # marker and edited on each push. This job checks nothing out and runs none # of the PR's code; it only reads the report the `diff` job uploaded. + # Dependabot's PRs are same-repo but get a read-only token, so they are + # skipped like fork PRs and keep the job summary and the artifact. comment: name: Post the diff on the PR needs: diff @@ -194,6 +198,7 @@ jobs: always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.user.login != 'dependabot[bot]' && (needs.diff.result == 'success' || needs.diff.result == 'failure') timeout-minutes: 5 runs-on: ubuntu-latest diff --git a/scripts/interface-diff.mjs b/scripts/interface-diff.mjs index ce4c9ff63c..0f40d0d904 100644 --- a/scripts/interface-diff.mjs +++ b/scripts/interface-diff.mjs @@ -56,6 +56,7 @@ import { import { createRequire } from "node:module"; import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; +import { winShellArgs } from "./lib/win-shell-args.mjs"; const repoRoot = path.resolve( path.dirname(fileURLToPath(import.meta.url)), @@ -191,14 +192,19 @@ export function renderReport(verdict, { base, head }) { return lines.join("\n"); } -/** Run a command to completion; throw with its output when it fails. */ +/** + * Run a command to completion; throw with its output when it fails. Only + * `npm` needs a shell, and only on Windows (it is a `.cmd` shim there); its + * arguments are then quoted for `cmd.exe`. `git` is spawned without one. + */ function run(command, args, cwd, env) { - const res = spawnSync(command, args, { + const shell = process.platform === "win32" && command === "npm"; + const res = spawnSync(command, shell ? winShellArgs(args) : args, { cwd, env: env ?? process.env, encoding: "utf8", maxBuffer: 64 * 1024 * 1024, - shell: process.platform === "win32", + shell, }); if (res.error) throw res.error; if (res.status !== 0) diff --git a/scripts/interface-diff.test.mjs b/scripts/interface-diff.test.mjs index 0d36cbffbf..20134909a5 100644 --- a/scripts/interface-diff.test.mjs +++ b/scripts/interface-diff.test.mjs @@ -37,8 +37,11 @@ test("parseArgs refuses an unknown flag or a missing value", () => { assert.throws(() => parseArgs(["--base="]), /--base needs a value/); }); -test("main exits 2 on a bad command line, before building anything", async () => { +test("main exits 2 on a bad command line, before building anything", async (t) => { + // The usage error it prints is expected; keep it out of the test output. + const error = t.mock.method(console, "error", () => {}); assert.equal(await main(["--nope"]), 2); + assert.match(String(error.mock.calls[0]?.arguments[0]), /unknown argument/); }); test("an identical interface is unchanged", () => { From b15cf214094b773487490c24ba79aa9d81ca5f2e Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sun, 4 Oct 2026 03:44:53 -0400 Subject: [PATCH 25/65] fix(interface-diff): give each push and release run its own group (#4860) Copilot round 4 on #4975: a concurrency group keeps only one pending run, so a third rapid push cancelled the queued one even without cancel-in-progress. Group by ref only for PRs; by run id otherwise. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .github/workflows/everything-mcp-diff.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/everything-mcp-diff.yml b/.github/workflows/everything-mcp-diff.yml index 8add0835c9..9520b235c7 100644 --- a/.github/workflows/everything-mcp-diff.yml +++ b/.github/workflows/everything-mcp-diff.yml @@ -70,10 +70,11 @@ on: permissions: contents: read -# Superseded runs on the same PR are cancelled; pushes and releases each keep -# their own run. +# Superseded runs on the same PR are cancelled. Every other run gets a group +# of its own: a group holds one running and one pending run, so a shared group +# would drop a push's run when two more queued behind it. concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: From fe4cef392799f5462a2fa61745aa9f4fc06ab6cf Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sun, 4 Oct 2026 03:51:55 -0400 Subject: [PATCH 26/65] fix(interface-diff): never pass a report without a verdict (#4860) Copilot round 5 on #4975: - A result with no boolean hasDifferences is an error, not "unchanged". - On a probe failure, report the CLI's contextual message ("Base server probe failed" / "Target probe failed") rather than the bare exception. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- scripts/interface-diff.mjs | 15 +++++++++++++-- scripts/interface-diff.test.mjs | 6 ++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/scripts/interface-diff.mjs b/scripts/interface-diff.mjs index 0f40d0d904..58c084f127 100644 --- a/scripts/interface-diff.mjs +++ b/scripts/interface-diff.mjs @@ -131,8 +131,19 @@ export function classify(stdout, stderr = "") { diffs: [], }; const diffs = Array.isArray(result.diffs) ? result.diffs : []; - if (result.error) - return { status: "error", error: String(result.error), diffs }; + if (result.error) { + // `error` is the bare exception; the entry in `diffs` says which build + // failed ("Base server probe failed: …" / "Target probe failed: …"). + const context = diffs.find((d) => d.endpoint === "error")?.diff; + return { status: "error", error: String(context ?? result.error), diffs }; + } + // A report without a verdict is not a pass. + if (typeof result.hasDifferences !== "boolean") + return { + status: "error", + error: "mcp-server-diff's report has no hasDifferences verdict", + diffs, + }; return { status: result.hasDifferences ? "changed" : "unchanged", diffs, diff --git a/scripts/interface-diff.test.mjs b/scripts/interface-diff.test.mjs index 20134909a5..90d6fc25ab 100644 --- a/scripts/interface-diff.test.mjs +++ b/scripts/interface-diff.test.mjs @@ -77,12 +77,13 @@ test("a probe failure is an error, though the CLI reports it as a difference", ( const v = classify( cliJson({ hasDifferences: true, - error: "Target probe failed: boom", + error: "boom", diffs: [{ endpoint: "error", diff: "Target probe failed: boom" }], }), ); assert.equal(v.status, "error"); - assert.match(v.error, /boom/); + // The context (which build failed) comes from the diffs entry. + assert.equal(v.error, "Target probe failed: boom"); }); test("output that is not the JSON report is an error, never a pass", () => { @@ -90,6 +91,7 @@ test("output that is not the JSON report is an error, never a pass", () => { assert.equal(classify("Fatal error: x").status, "error"); assert.equal(classify("{}").status, "error"); assert.equal(classify(JSON.stringify({ results: [] })).status, "error"); + assert.equal(classify(JSON.stringify({ results: [{}] })).status, "error"); }); test("a CLI crash before the report carries its stderr as the reason", () => { From ff191795dcb1dc70f25da0adc638d49de1b5f50e Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sun, 4 Oct 2026 04:02:20 -0400 Subject: [PATCH 27/65] fix(interface-diff): bound the job summary; watch the Windows helper (#4860) Copilot round 6 on #4975: - Cap the job summary below GitHub's 1 MiB limit, pointing to the artifact when the report is longer. - Add scripts/lib/win-shell-args.mjs, which the script imports, to the path filters. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .github/workflows/everything-mcp-diff.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/everything-mcp-diff.yml b/.github/workflows/everything-mcp-diff.yml index 9520b235c7..144eacfd79 100644 --- a/.github/workflows/everything-mcp-diff.yml +++ b/.github/workflows/everything-mcp-diff.yml @@ -38,6 +38,7 @@ on: - "package-lock.json" - "tsconfig.json" - "scripts/interface-diff.mjs" + - "scripts/lib/win-shell-args.mjs" - ".github/workflows/everything-mcp-diff.yml" # No branch filter: PRs to v2/main and to main both run, and so does a PR # stacked on another PR's branch. The base is the PR's own base either way. @@ -48,6 +49,7 @@ on: - "package-lock.json" - "tsconfig.json" - "scripts/interface-diff.mjs" + - "scripts/lib/win-shell-args.mjs" - ".github/workflows/everything-mcp-diff.yml" # A published Release (a milestone, tagged vX.Y.Z on main; see RELEASING.md) # surfaces the cumulative interface delta since the previous Release. This @@ -160,7 +162,12 @@ jobs: run: | { if [ -f mcp-diff-report/report.md ]; then - cat mcp-diff-report/report.md + # A step summary holds at most 1 MiB. + head -c 900000 mcp-diff-report/report.md + if [ "$(wc -c < mcp-diff-report/report.md)" -gt 900000 ]; then + echo "" + echo "_The report is truncated here; the full report is in the run's \`mcp-diff-report\` artifact._" + fi else echo "The interface diff produced no report; see the job log." fi From 9d7128ca3b5b98398e88c9ea90279b69b7f24c2b Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sun, 4 Oct 2026 04:24:53 -0400 Subject: [PATCH 28/65] fix(interface-diff): drop target_ref; keep both streams in errors (#4860) Copilot round 7 on #4975: - Remove the dispatch `target_ref` input: a target that predates the script has no `npm run interface-diff`, so the run failed. The head is the branch chosen in "Use workflow from". - A failed base install or build now reports the tail of both stdout and stderr, so tsc diagnostics are not lost behind an npm footer. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .github/workflows/everything-mcp-diff.yml | 11 +++++------ scripts/interface-diff.mjs | 8 +++++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/everything-mcp-diff.yml b/.github/workflows/everything-mcp-diff.yml index 144eacfd79..16fe49c17d 100644 --- a/.github/workflows/everything-mcp-diff.yml +++ b/.github/workflows/everything-mcp-diff.yml @@ -57,17 +57,16 @@ on: # 2024, and no release since has used them. release: types: [published] - # Manual trigger for comparing any two refs (commits, tags, branches) + # Manual trigger: the branch chosen in "Use workflow from" is the head, and + # `compare_ref` any base. #3260 also took a `target_ref`, but a target that + # predates the interface-diff script has no `npm run interface-diff` to run, + # so the head is the dispatched branch instead. workflow_dispatch: inputs: compare_ref: description: "Base ref to compare against (commit SHA, tag, or branch; defaults to the merge-base with v2/main)" required: false default: "" - target_ref: - description: "Target ref to compare (defaults to the ref the workflow runs on)" - required: false - default: "" permissions: contents: read @@ -95,7 +94,7 @@ jobs: fetch-depth: 0 # The event's own commit (the merge commit for a PR), not a branch # ref that a later push could move before this step runs. - ref: ${{ inputs.target_ref || github.sha }} + ref: ${{ github.sha }} persist-credentials: false - uses: actions/setup-node@v7 diff --git a/scripts/interface-diff.mjs b/scripts/interface-diff.mjs index 58c084f127..59403326c4 100644 --- a/scripts/interface-diff.mjs +++ b/scripts/interface-diff.mjs @@ -203,6 +203,12 @@ export function renderReport(verdict, { base, head }) { return lines.join("\n"); } +/** The last lines of a stream, for an error message; empty stays empty. */ +const tail = (text) => { + const lines = (text ?? "").trim(); + return lines ? `${lines.split("\n").slice(-25).join("\n")}\n` : ""; +}; + /** * Run a command to completion; throw with its output when it fails. Only * `npm` needs a shell, and only on Windows (it is a `.cmd` shim there); its @@ -220,7 +226,7 @@ function run(command, args, cwd, env) { if (res.error) throw res.error; if (res.status !== 0) throw new Error( - `\`${command} ${args.join(" ")}\` exited ${res.status}:\n${(res.stderr || res.stdout).trim().split("\n").slice(-25).join("\n")}`, + `\`${command} ${args.join(" ")}\` exited ${res.status}:\n${tail(res.stdout)}${tail(res.stderr)}`, ); return res.stdout; } From 6eebb0c65045c839448e8623ddced0ea5ffd806e Mon Sep 17 00:00:00 2001 From: cliffhall Date: Fri, 9 Oct 2026 23:25:40 -0400 Subject: [PATCH 29/65] chore(interface-diff): refresh the lockfile after rebasing onto v2/main (#4860) v2/main no longer carries SDK v1 at the root (#5063), so mcp-server-diff's @modelcontextprotocol/sdk ^1.13.2 now resolves to its own dev-only copy. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- package-lock.json | 140 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/package-lock.json b/package-lock.json index 6359f597db..a9adead5f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -622,6 +622,19 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, + "node_modules/@hono/node-server": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.4.tgz", + "integrity": "sha512-2g1qeHl4BduwDn6L8nkAvDrPUWPMlkQ//a5NhP/zIyaXKMjZNRV+cG9Csu+BiJFf6+68pqzjf4Hi5sJGKHuS1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "hono": "^4 || ^5.0.0-0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -841,6 +854,47 @@ "hono": "^4" } }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.32.1.tgz", + "integrity": "sha512-2DdE+SJDtzLEEWzY1ZjY7Q+VcPhcV1KisD3zI4u0XZyktsjHum1mwbMI+JaulUBi2OZk+KJAi2uPXzxichPkdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9 || ^2.0.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, "node_modules/@modelcontextprotocol/server": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.3.1.tgz", @@ -1847,6 +1901,41 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -2829,6 +2918,23 @@ "fast-string-truncated-width": "^3.0.2" } }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fast-wrap-ansi": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", @@ -3447,6 +3553,20 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -4518,6 +4638,16 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", @@ -5520,6 +5650,16 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + }, "src/everything": { "name": "@modelcontextprotocol/server-everything", "version": "1.0.0", From d52c223121209f44c30ff867d9fc4665155f91de Mon Sep 17 00:00:00 2001 From: cliffhall Date: Fri, 9 Oct 2026 23:46:10 -0400 Subject: [PATCH 30/65] time: accept only exact IANA timezone keys on every platform On macOS, zoneinfo resolves a key such as europe/warsaw through the case-insensitive APFS tz database, so the server accepted it where Linux rejects it, and test_get_current_time_errors failed locally. Check each key against available_timezones() in the tool path and in the --local-timezone validation. Closes #5060 Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/time/README.md | 2 ++ src/time/src/mcp_server_time/server.py | 28 +++++++++++++++++++++++--- src/time/tests/test_protocol.py | 1 + src/time/tests/test_server.py | 21 ++++++++++++++++++- 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/time/README.md b/src/time/README.md index 2b878eab81..6cb87ee4b6 100644 --- a/src/time/README.md +++ b/src/time/README.md @@ -20,6 +20,8 @@ Requires MCP Python SDK 2.x (`mcp>=2.2,<3`). - `time` (string): Time in 24-hour format (HH:MM) - `target_timezone` (string): Target IANA timezone name +A timezone name, here and in `--local-timezone`, must be an exact IANA key, case included: `Europe/Warsaw` is accepted and `europe/warsaw` is rejected on every platform, including macOS, whose case-insensitive filesystem would otherwise resolve it. + ## Installation ### Using uv (recommended) diff --git a/src/time/src/mcp_server_time/server.py b/src/time/src/mcp_server_time/server.py index 9f56bb28fa..92e2704d7b 100644 --- a/src/time/src/mcp_server_time/server.py +++ b/src/time/src/mcp_server_time/server.py @@ -9,13 +9,14 @@ # loop only; serving 2026-07-28 is #4853. from datetime import datetime, timedelta +from functools import cache from importlib.metadata import version from enum import Enum import json import sys from typing import Any -from zoneinfo import ZoneInfo, ZoneInfoNotFoundError +from zoneinfo import ZoneInfo, ZoneInfoNotFoundError, available_timezones from tzlocal import get_localzone_name # ← returns "Europe/Paris", etc. import jsonschema @@ -79,9 +80,30 @@ def get_local_tz(local_tz_override: str | None = None) -> ZoneInfo: return ZoneInfo("UTC") +@cache +def known_timezones() -> frozenset[str]: + """The exact IANA keys this host's tz database holds, read once.""" + return frozenset(available_timezones()) + + +def load_zoneinfo(timezone_name: str) -> ZoneInfo: + """`ZoneInfo(timezone_name)`, accepting only an exact IANA key. + + zoneinfo looks a key up as a file path, so on a case-insensitive + filesystem (macOS's APFS) `europe/warsaw` resolves to `Europe/Warsaw` + while Linux rejects it (#5060). Checking the key against the database's + own list makes every platform reject it. `ZoneInfo` runs first so a path + or a directory keeps the error zoneinfo gives it. + """ + zone = ZoneInfo(timezone_name) + if timezone_name not in known_timezones(): + raise ZoneInfoNotFoundError(f"No time zone found with key {timezone_name}") + return zone + + def get_zoneinfo(timezone_name: str) -> ZoneInfo: try: - return ZoneInfo(timezone_name) + return load_zoneinfo(timezone_name) except Exception as e: raise MCPError(code=INVALID_PARAMS, message=f"Invalid timezone: {str(e)}") @@ -173,7 +195,7 @@ async def serve(local_timezone: str | None = None) -> None: # bad key (unknown name, a tzdata directory, a path, a null byte), and # its messages can carry a filesystem path, so none of them is shown. try: - ZoneInfo(local_timezone) + load_zoneinfo(local_timezone) except (ZoneInfoNotFoundError, ValueError, OSError): sys.exit( f"Error: invalid --local-timezone {local_timezone!r}: " diff --git a/src/time/tests/test_protocol.py b/src/time/tests/test_protocol.py index e2ab1d6384..5c2246097e 100644 --- a/src/time/tests/test_protocol.py +++ b/src/time/tests/test_protocol.py @@ -250,6 +250,7 @@ async def test_empty_local_timezone_override_is_treated_as_absent() -> None: "Not/AZone", # unknown name: ZoneInfoNotFoundError "America", # a tzdata directory, not a zone: OSError "../etc/passwd", # outside TZPATH: ValueError + "europe/warsaw", # not the exact key, on any filesystem (#5060) ], ) async def test_invalid_local_timezone_fails_before_the_transport_opens( diff --git a/src/time/tests/test_server.py b/src/time/tests/test_server.py index a197daa07a..a64ecc863f 100644 --- a/src/time/tests/test_server.py +++ b/src/time/tests/test_server.py @@ -4,7 +4,12 @@ from unittest.mock import patch from zoneinfo import ZoneInfo -from mcp_server_time.server import TimeServer, get_local_tz +from mcp_server_time.server import ( + TimeServer, + get_local_tz, + get_zoneinfo, + known_timezones, +) @pytest.mark.parametrize( @@ -525,3 +530,17 @@ def test_get_local_tz_various_timezones(mock_get_localzone, timezone_name): result = get_local_tz() assert str(result) == timezone_name assert isinstance(result, ZoneInfo) + + +def test_get_zoneinfo_rejects_a_key_the_tz_database_does_not_list(): + # #5060: on a case-insensitive filesystem `ZoneInfo` resolves a key that + # is not an exact IANA name. Simulate that on any platform: `ZoneInfo` + # accepts "Europe/Warsaw", but the database's key list does not hold it. + without_warsaw = known_timezones() - {"Europe/Warsaw"} + with patch("mcp_server_time.server.known_timezones", return_value=without_warsaw): + with pytest.raises( + MCPError, + match="Invalid timezone: 'No time zone found with key Europe/Warsaw'", + ): + get_zoneinfo("Europe/Warsaw") + assert str(get_zoneinfo("Europe/Warsaw")) == "Europe/Warsaw" From d7388c6408754374b034d6a104094159c145b69d Mon Sep 17 00:00:00 2001 From: cliffhall Date: Fri, 9 Oct 2026 23:46:29 -0400 Subject: [PATCH 31/65] test(memory): expect Node 26's path-suffixed EISDIR message Node 26 appends the file path to the EISDIR read error, so the exact-match assertion in persistence.test.ts failed there. Expect the suffixed text on Node 26 and later and the bare text before, keeping the exact match. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/memory/__tests__/persistence.test.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/memory/__tests__/persistence.test.ts b/src/memory/__tests__/persistence.test.ts index 24ac8c2a57..9f867bed5b 100644 --- a/src/memory/__tests__/persistence.test.ts +++ b/src/memory/__tests__/persistence.test.ts @@ -211,6 +211,11 @@ describe("memory persistence over the protocol", () => { // which is not the ENOENT that means "no graph yet". await fs.mkdir(filePath); const { client } = await open(); + // Node 26 appends the path to the message; earlier versions do not + // (#5076). The server passes Node's message through either way. + const bare = "EISDIR: illegal operation on a directory, read"; + const nodeMajor = Number(process.versions.node.split(".")[0]); + const expected = nodeMajor >= 26 ? `${bare} '${filePath}'` : bare; for (const [name, args] of [ ["read_graph", {}], @@ -220,9 +225,7 @@ describe("memory persistence over the protocol", () => { ] as const) { const result = await call(client, name, args); expect(result.isError).toBe(true); - expect(textOf(result)).toBe( - "EISDIR: illegal operation on a directory, read", - ); + expect(textOf(result)).toBe(expected); } await expect( client.readResource({ uri: "memory://knowledge-graph" }), From cd34c3b0f91c851a8052399a4d6dbe8f37ec51f5 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 01:02:46 -0400 Subject: [PATCH 32/65] chore(deps): pin proxy-addr to ^2.0.8 (GHSA-jqcg-44mw-7w3h) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- package-lock.json | 10 +++++++--- package.json | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index a9adead5f4..21cc802b98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4489,9 +4489,9 @@ } }, "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -4499,6 +4499,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/pump": { diff --git a/package.json b/package.json index 49ed4ba99e..d66229c32f 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ }, "overrides": { "qs": ">=6.15.2", - "hono": ">=4.12.21" + "hono": ">=4.12.21", + "proxy-addr": "^2.0.8" } } From 916ccca7bfa0357d7d65ef6f7f5d610ad21d49d6 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 01:02:46 -0400 Subject: [PATCH 33/65] chore(deps): pin ip-address to ^10.7.1 (GHSA-mwp4-54f8-5fhr and six more) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- package-lock.json | 6 +++--- package.json | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 21cc802b98..3b563fc491 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3386,9 +3386,9 @@ } }, "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.3.tgz", + "integrity": "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==", "license": "MIT", "engines": { "node": ">= 12" diff --git a/package.json b/package.json index d66229c32f..d32ec469b8 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "overrides": { "qs": ">=6.15.2", "hono": ">=4.12.21", - "proxy-addr": "^2.0.8" + "proxy-addr": "^2.0.8", + "ip-address": "^10.7.1" } } From 5f0b9b99deecc175f7708507c2be1ff0274416b1 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 01:02:47 -0400 Subject: [PATCH 34/65] chore(deps): pin brace-expansion to ^5.0.12 (GHSA-rgw5-rvv9-x895 and three more) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- package-lock.json | 6 +++--- package.json | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3b563fc491..6f381ad645 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2005,9 +2005,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", - "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" diff --git a/package.json b/package.json index d32ec469b8..29465dd222 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "qs": ">=6.15.2", "hono": ">=4.12.21", "proxy-addr": "^2.0.8", - "ip-address": "^10.7.1" + "ip-address": "^10.7.1", + "brace-expansion": "^5.0.12" } } From ed640a12a412c729acaf45e243fc0ed63e6ae590 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 01:02:47 -0400 Subject: [PATCH 35/65] chore(deps): raise the hono override to >=4.13.7 (GHSA-8j4g-w8fx-2239 and seven more) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6f381ad645..d1c6b9dbe6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3264,9 +3264,9 @@ } }, "node_modules/hono": { - "version": "4.12.32", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.32.tgz", - "integrity": "sha512-XcuyW9qE2kJn07PkecMOBd5Vq/hMy7mmGw+idz1yblbg9N17ijJODrvPkn7/dwL3Kulj8LcRJ69DLOWf91dRUg==", + "version": "4.13.13", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.13.tgz", + "integrity": "sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==", "license": "MIT", "engines": { "node": ">=16.9.0" diff --git a/package.json b/package.json index 29465dd222..3995650a3a 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ }, "overrides": { "qs": ">=6.15.2", - "hono": ">=4.12.21", + "hono": ">=4.13.7", "proxy-addr": "^2.0.8", "ip-address": "^10.7.1", "brace-expansion": "^5.0.12" From c88aa6dbf65b0daaab7d0dfc6973cc19446e0b32 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 01:02:47 -0400 Subject: [PATCH 36/65] chore(deps): raise the qs override to >=6.16.0 (GHSA-x5fp-wj9c-mxmx, GHSA-4mjr-xmp4-gh2g) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- package-lock.json | 9 +++++---- package.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d1c6b9dbe6..aad36ae4fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4547,12 +4547,13 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" diff --git a/package.json b/package.json index 3995650a3a..62287e50d2 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "yaml": "^2.9.0" }, "overrides": { - "qs": ">=6.15.2", + "qs": ">=6.16.0", "hono": ">=4.13.7", "proxy-addr": "^2.0.8", "ip-address": "^10.7.1", From d261885b799e89f988c4f5ffd23746cc8f332ccf Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 01:03:05 -0400 Subject: [PATCH 37/65] chore(deps): pin nanoid to ^3.3.18 (GHSA-2v37-7h3g-55p8) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- package-lock.json | 6 +++--- package.json | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index aad36ae4fc..01386d4635 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4124,9 +4124,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", - "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index 62287e50d2..33a49082a8 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "hono": ">=4.13.7", "proxy-addr": "^2.0.8", "ip-address": "^10.7.1", - "brace-expansion": "^5.0.12" + "brace-expansion": "^5.0.12", + "nanoid": "^3.3.18" } } From fb942ea050c860fe5cd7be172ba7e17b2ab949cd Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 01:03:05 -0400 Subject: [PATCH 38/65] chore(deps): pin source-map-js to ^1.2.2 (GHSA-68fv-2mgg-jv7q) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- package-lock.json | 6 +++--- package.json | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 01386d4635..aa59d49cfd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5018,9 +5018,9 @@ "license": "MIT" }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { diff --git a/package.json b/package.json index 33a49082a8..f63222a12b 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "proxy-addr": "^2.0.8", "ip-address": "^10.7.1", "brace-expansion": "^5.0.12", - "nanoid": "^3.3.18" + "nanoid": "^3.3.18", + "source-map-js": "^1.2.2" } } From 12efa9b121f848d98fada6766b4afacb8944891f Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 01:03:11 -0400 Subject: [PATCH 39/65] chore(deps): bump vitest and @vitest/coverage-v8 to ^4.1.11 (GHSA-82fw-gwwq-j7x9) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- package-lock.json | 540 +++++++++++++--------------- src/everything/package.json | 4 +- src/filesystem/package.json | 4 +- src/memory/package.json | 4 +- src/sequentialthinking/package.json | 4 +- 5 files changed, 256 insertions(+), 300 deletions(-) diff --git a/package-lock.json b/package-lock.json index aa59d49cfd..20e5533cdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -460,40 +460,6 @@ "node": ">= 20.12.0" } }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", @@ -951,25 +917,6 @@ "resolved": "src/sequentialthinking", "link": true }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", - "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1009,13 +956,13 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.133.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.153.0.tgz", + "integrity": "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==", "dev": true, "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" } }, "node_modules/@pnpm/deps.graph-sequencer": { @@ -1031,10 +978,27 @@ "url": "https://opencollective.com/pnpm" } }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.13.tgz", + "integrity": "sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.13.tgz", + "integrity": "sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ==", "cpu": [ "arm64" ], @@ -1049,9 +1013,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", - "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.13.tgz", + "integrity": "sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA==", "cpu": [ "arm64" ], @@ -1066,9 +1030,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.13.tgz", + "integrity": "sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw==", "cpu": [ "x64" ], @@ -1083,9 +1047,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.13.tgz", + "integrity": "sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ==", "cpu": [ "x64" ], @@ -1100,9 +1064,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.13.tgz", + "integrity": "sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg==", "cpu": [ "arm" ], @@ -1117,13 +1081,16 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.13.tgz", + "integrity": "sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1134,13 +1101,16 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.13.tgz", + "integrity": "sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1151,13 +1121,16 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.13.tgz", + "integrity": "sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1168,13 +1141,16 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.13.tgz", + "integrity": "sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1185,13 +1161,16 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.13.tgz", + "integrity": "sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1202,13 +1181,16 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.13.tgz", + "integrity": "sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1219,9 +1201,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.13.tgz", + "integrity": "sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==", "cpu": [ "arm64" ], @@ -1235,29 +1217,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.13.tgz", + "integrity": "sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ==", "cpu": [ "arm64" ], @@ -1272,9 +1235,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.13.tgz", + "integrity": "sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg==", "cpu": [ "x64" ], @@ -1302,17 +1265,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -1722,14 +1674,14 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz", - "integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.11.tgz", + "integrity": "sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.8", + "@vitest/utils": "4.1.11", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -1743,8 +1695,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.8", - "vitest": "4.1.8" + "@vitest/browser": "4.1.11", + "vitest": "4.1.11" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -1753,16 +1705,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", - "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.8", - "@vitest/utils": "4.1.8", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -1771,13 +1723,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", - "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.8", + "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -1798,9 +1750,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", - "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", "dev": true, "license": "MIT", "dependencies": { @@ -1811,13 +1763,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", - "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.8", + "@vitest/utils": "4.1.11", "pathe": "^2.0.3" }, "funding": { @@ -1825,14 +1777,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", - "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.8", - "@vitest/utils": "4.1.8", + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -1841,9 +1793,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", - "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", "dev": true, "license": "MIT", "funding": { @@ -1851,13 +1803,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", - "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.8", + "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -2092,9 +2044,9 @@ } }, "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", "dev": true, "license": "MIT", "engines": { @@ -2420,9 +2372,9 @@ } }, "node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", "dev": true, "license": "MIT" }, @@ -2793,9 +2745,9 @@ } }, "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3638,9 +3590,9 @@ } }, "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -3654,23 +3606,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -3689,9 +3641,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], @@ -3710,9 +3662,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], @@ -3731,9 +3683,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], @@ -3752,9 +3704,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], @@ -3773,13 +3725,16 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3794,13 +3749,16 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3815,13 +3773,16 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3836,13 +3797,16 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3857,9 +3821,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -3878,9 +3842,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -4210,9 +4174,9 @@ } }, "node_modules/obug": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", - "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -4416,9 +4380,9 @@ } }, "node_modules/postcss": { - "version": "8.5.25", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", - "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "version": "8.5.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", "dev": true, "funding": [ { @@ -4436,9 +4400,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.16", + "nanoid": "^3.3.19", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -4697,13 +4661,13 @@ } }, "node_modules/rolldown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.13.tgz", + "integrity": "sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.133.0", + "@oxc-project/types": "=0.153.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -4713,21 +4677,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.3", - "@rolldown/binding-darwin-arm64": "1.0.3", - "@rolldown/binding-darwin-x64": "1.0.3", - "@rolldown/binding-freebsd-x64": "1.0.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", - "@rolldown/binding-linux-arm64-gnu": "1.0.3", - "@rolldown/binding-linux-arm64-musl": "1.0.3", - "@rolldown/binding-linux-ppc64-gnu": "1.0.3", - "@rolldown/binding-linux-s390x-gnu": "1.0.3", - "@rolldown/binding-linux-x64-gnu": "1.0.3", - "@rolldown/binding-linux-x64-musl": "1.0.3", - "@rolldown/binding-openharmony-arm64": "1.0.3", - "@rolldown/binding-wasm32-wasi": "1.0.3", - "@rolldown/binding-win32-arm64-msvc": "1.0.3", - "@rolldown/binding-win32-x64-msvc": "1.0.3" + "@rolldown/binding-android-arm-eabi": "1.2.13", + "@rolldown/binding-android-arm64": "1.2.13", + "@rolldown/binding-darwin-arm64": "1.2.13", + "@rolldown/binding-darwin-x64": "1.2.13", + "@rolldown/binding-freebsd-x64": "1.2.13", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", + "@rolldown/binding-linux-arm64-gnu": "1.2.13", + "@rolldown/binding-linux-arm64-musl": "1.2.13", + "@rolldown/binding-linux-ppc64-gnu": "1.2.13", + "@rolldown/binding-linux-s390x-gnu": "1.2.13", + "@rolldown/binding-linux-x64-gnu": "1.2.13", + "@rolldown/binding-linux-x64-musl": "1.2.13", + "@rolldown/binding-openharmony-arm64": "1.2.13", + "@rolldown/binding-win32-arm64-msvc": "1.2.13", + "@rolldown/binding-win32-x64-msvc": "1.2.13" } }, "node_modules/router": { @@ -5044,9 +5008,9 @@ } }, "node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz", + "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==", "dev": true, "license": "MIT" }, @@ -5130,9 +5094,9 @@ } }, "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.2.0.tgz", + "integrity": "sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==", "dev": true, "license": "MIT", "engines": { @@ -5174,14 +5138,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", @@ -5324,16 +5280,16 @@ } }, "node_modules/vite": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz", + "integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.29", + "rolldown": "~1.2.12", "tinyglobby": "^0.2.17" }, "bin": { @@ -5350,7 +5306,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", + "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -5402,19 +5358,19 @@ } }, "node_modules/vitest": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", - "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.8", - "@vitest/mocker": "4.1.8", - "@vitest/pretty-format": "4.1.8", - "@vitest/runner": "4.1.8", - "@vitest/snapshot": "4.1.8", - "@vitest/spy": "4.1.8", - "@vitest/utils": "4.1.8", + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -5442,12 +5398,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.8", - "@vitest/browser-preview": "4.1.8", - "@vitest/browser-webdriverio": "4.1.8", - "@vitest/coverage-istanbul": "4.1.8", - "@vitest/coverage-v8": "4.1.8", - "@vitest/ui": "4.1.8", + "@vitest/browser-playwright": "4.1.11", + "@vitest/browser-preview": "4.1.11", + "@vitest/browser-webdriverio": "4.1.11", + "@vitest/coverage-istanbul": "4.1.11", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -5686,10 +5642,10 @@ "@modelcontextprotocol/core": "^2.3.1", "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@vitest/coverage-v8": "^4.1.8", + "@vitest/coverage-v8": "^4.1.11", "shx": "^0.4.0", "typescript": "^5.8.2", - "vitest": "^4.1.8" + "vitest": "^4.1.11" }, "engines": { "node": ">=20" @@ -5714,10 +5670,10 @@ "@types/diff": "^5.0.9", "@types/minimatch": "^5.1.2", "@types/node": "^22", - "@vitest/coverage-v8": "^4.1.8", + "@vitest/coverage-v8": "^4.1.11", "shx": "^0.4.0", "typescript": "^5.8.2", - "vitest": "^4.1.8" + "vitest": "^4.1.11" }, "engines": { "node": ">=20" @@ -5737,10 +5693,10 @@ "devDependencies": { "@modelcontextprotocol/client": "^2.3.1", "@types/node": "^22", - "@vitest/coverage-v8": "^4.1.8", + "@vitest/coverage-v8": "^4.1.11", "shx": "^0.4.0", "typescript": "^5.8.2", - "vitest": "^4.1.8" + "vitest": "^4.1.11" }, "engines": { "node": ">=20" @@ -5763,10 +5719,10 @@ "@modelcontextprotocol/client": "^2.3.1", "@types/node": "^22", "@types/yargs": "^17.0.32", - "@vitest/coverage-v8": "^4.1.8", + "@vitest/coverage-v8": "^4.1.11", "shx": "^0.4.0", "typescript": "^5.8.2", - "vitest": "^4.1.8" + "vitest": "^4.1.11" }, "engines": { "node": ">=20" diff --git a/src/everything/package.json b/src/everything/package.json index 51ce62ead7..ecdb17ce3f 100644 --- a/src/everything/package.json +++ b/src/everything/package.json @@ -50,9 +50,9 @@ "@modelcontextprotocol/core": "^2.3.1", "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@vitest/coverage-v8": "^4.1.8", + "@vitest/coverage-v8": "^4.1.11", "shx": "^0.4.0", "typescript": "^5.8.2", - "vitest": "^4.1.8" + "vitest": "^4.1.11" } } diff --git a/src/filesystem/package.json b/src/filesystem/package.json index 2badd52383..8adbd53b22 100644 --- a/src/filesystem/package.json +++ b/src/filesystem/package.json @@ -45,9 +45,9 @@ "@types/diff": "^5.0.9", "@types/minimatch": "^5.1.2", "@types/node": "^22", - "@vitest/coverage-v8": "^4.1.8", + "@vitest/coverage-v8": "^4.1.11", "shx": "^0.4.0", "typescript": "^5.8.2", - "vitest": "^4.1.8" + "vitest": "^4.1.11" } } diff --git a/src/memory/package.json b/src/memory/package.json index 3d5fb93444..e48e521b9d 100644 --- a/src/memory/package.json +++ b/src/memory/package.json @@ -40,9 +40,9 @@ "devDependencies": { "@modelcontextprotocol/client": "^2.3.1", "@types/node": "^22", - "@vitest/coverage-v8": "^4.1.8", + "@vitest/coverage-v8": "^4.1.11", "shx": "^0.4.0", "typescript": "^5.8.2", - "vitest": "^4.1.8" + "vitest": "^4.1.11" } } diff --git a/src/sequentialthinking/package.json b/src/sequentialthinking/package.json index 42fa282ed9..ae41494e13 100644 --- a/src/sequentialthinking/package.json +++ b/src/sequentialthinking/package.json @@ -43,9 +43,9 @@ "@modelcontextprotocol/client": "^2.3.1", "@types/node": "^22", "@types/yargs": "^17.0.32", - "@vitest/coverage-v8": "^4.1.8", + "@vitest/coverage-v8": "^4.1.11", "shx": "^0.4.0", "typescript": "^5.8.2", - "vitest": "^4.1.8" + "vitest": "^4.1.11" } } From 73fe3283828451a727b9daba9ddd3109f649fead Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 01:03:29 -0400 Subject: [PATCH 40/65] chore(fetch): require protego>=0.6.2 (PYSEC-2026-3906) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/fetch/pyproject.toml | 2 +- src/fetch/uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/fetch/pyproject.toml b/src/fetch/pyproject.toml index 0666d3b768..2f596c278a 100644 --- a/src/fetch/pyproject.toml +++ b/src/fetch/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ "markdownify>=0.13.1", "jsonschema>=4.20.0", "mcp>=2.2,<3", - "protego>=0.3.1", + "protego>=0.6.2", "pydantic>=2.0.0", "readabilipy>=0.2.0", "requests>=2.32.3", diff --git a/src/fetch/uv.lock b/src/fetch/uv.lock index dc2c712ded..8cc539425a 100644 --- a/src/fetch/uv.lock +++ b/src/fetch/uv.lock @@ -760,7 +760,7 @@ requires-dist = [ { name = "jsonschema", specifier = ">=4.20.0" }, { name = "markdownify", specifier = ">=0.13.1" }, { name = "mcp", specifier = ">=2.2,<3" }, - { name = "protego", specifier = ">=0.3.1" }, + { name = "protego", specifier = ">=0.6.2" }, { name = "pydantic", specifier = ">=2.0.0" }, { name = "readabilipy", specifier = ">=0.2.0" }, { name = "requests", specifier = ">=2.32.3" }, @@ -829,11 +829,11 @@ wheels = [ [[package]] name = "protego" -version = "0.3.1" +version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/12/cab9fa77ff4e9e444a5eb5480db4b4f872c03aa079145804aa054be377bc/Protego-0.3.1.tar.gz", hash = "sha256:e94430d0d25cbbf239bc849d86c5e544fbde531fcccfa059953c7da344a1712c", size = 3246145, upload-time = "2024-04-05T10:08:54.884Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/d9/5026b9e75db1172f02441a84eaf42efb199b4cea14dda7651a620d1acd40/protego-0.7.0.tar.gz", hash = "sha256:2c032d9736a1f4f0c4318f3558353ae34da5cd038f1a5e064ded7548df315e5a", size = 3144321, upload-time = "2026-09-21T10:54:00.107Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/ef/ece78585a5a189d8cc2b4c2d2b92a0dc025f156a6501159b026472ebbedc/Protego-0.3.1-py2.py3-none-any.whl", hash = "sha256:2fbe8e9b7a7dbc5016a932b14c98d236aad4c29290bbe457b8d2779666ef7a41", size = 8474, upload-time = "2024-04-05T10:08:53.5Z" }, + { url = "https://files.pythonhosted.org/packages/c0/20/e4382a85e4cf4746d11746481f83360b990165725dd36ba73d55e029e6a9/protego-0.7.0-py3-none-any.whl", hash = "sha256:944638aaee608f6c83fcad442695f3f9381a60be8d5007248f46b00f201fa5fc", size = 13714, upload-time = "2026-09-21T10:53:58.812Z" }, ] [[package]] From c8cee7151b8a434be47a68e8ab2d434054013b57 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 01:03:30 -0400 Subject: [PATCH 41/65] chore(git): require gitpython>=3.1.60 (PYSEC-2026-3783 and fourteen more) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/git/pyproject.toml | 2 +- src/git/uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/git/pyproject.toml b/src/git/pyproject.toml index cf6374776e..d9fda96b55 100644 --- a/src/git/pyproject.toml +++ b/src/git/pyproject.toml @@ -17,7 +17,7 @@ classifiers = [ ] dependencies = [ "click>=8.1.7", - "gitpython>=3.1.50", + "gitpython>=3.1.60", "jsonschema>=4.20.0", "mcp>=2.2,<3", "pydantic>=2.0.0", diff --git a/src/git/uv.lock b/src/git/uv.lock index 92d67b2a77..821074839e 100644 --- a/src/git/uv.lock +++ b/src/git/uv.lock @@ -366,14 +366,14 @@ wheels = [ [[package]] name = "gitpython" -version = "3.1.54" +version = "3.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "gitdb" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/d5/3da0b92033887033f4c27f2dd109a303c4ca62813c7b3bb2511edb4777de/gitpython-3.1.54.tar.gz", hash = "sha256:53f2085e24a2cda300eed7c3fc5f1559ae289634b725e98acaf4791940247aa0", size = 225076, upload-time = "2026-07-22T04:08:51.403Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/2d/6f6e649818da44d4499604802c89329b8d9799687a124e3a5e467a643336/gitpython-3.2.0.tar.gz", hash = "sha256:fb92310af6844d96adc95ca066ed2e617c00e1dbd146a326626c81e72e18cc2e", size = 238091, upload-time = "2026-09-30T09:10:32.65Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/b9/876f442a28df5c068ca69b0122d5c35e65fd2d2fa9992ea5cb5944ea00a6/gitpython-3.1.54-py3-none-any.whl", hash = "sha256:b90d7b3d9bc0238681d24369130826f0dcdb0ceaa45db67cf1d4ffa4c302dedf", size = 216575, upload-time = "2026-07-22T04:08:50.05Z" }, + { url = "https://files.pythonhosted.org/packages/fa/48/f36d233ce749d1bb880cb5e6f8bb171dc2effabc7435c31da604d831b3ec/gitpython-3.2.0-py3-none-any.whl", hash = "sha256:bd70c5ec05cd2b797423e7eb312147d2458d3cca92085888fba2213f85905537", size = 228537, upload-time = "2026-09-30T09:10:31.159Z" }, ] [[package]] @@ -518,7 +518,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "click", specifier = ">=8.1.7" }, - { name = "gitpython", specifier = ">=3.1.50" }, + { name = "gitpython", specifier = ">=3.1.60" }, { name = "jsonschema", specifier = ">=4.20.0" }, { name = "mcp", specifier = ">=2.2,<3" }, { name = "pydantic", specifier = ">=2.0.0" }, From ce54c890dff348b437b04a48f102ea513b111b35 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 01:03:30 -0400 Subject: [PATCH 42/65] chore(python): lock cryptography 50 (PYSEC-2026-3552) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/fetch/uv.lock | 107 ++++++++++++++++++++++++++-------------------- src/git/uv.lock | 107 ++++++++++++++++++++++++++-------------------- src/time/uv.lock | 107 ++++++++++++++++++++++++++-------------------- 3 files changed, 180 insertions(+), 141 deletions(-) diff --git a/src/fetch/uv.lock b/src/fetch/uv.lock index 8cc539425a..64c1c7b94b 100644 --- a/src/fetch/uv.lock +++ b/src/fetch/uv.lock @@ -370,59 +370,72 @@ toml = [ [[package]] name = "cryptography" -version = "49.0.0" +version = "50.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/af/182eb91b0df3fe75c4d9f26fe70684569566745f6ba7e5c9c73a862c5252/cryptography-50.0.2.tar.gz", hash = "sha256:7b46165bb56eb4704e2eaaf86f3c940d19154535d9b0ca7d6d590b04060e00d5", size = 880623, upload-time = "2026-09-30T15:30:04.884Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, - { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, - { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, - { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, - { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, - { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, - { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, - { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, - { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, - { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, - { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, - { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, - { url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" }, - { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" }, - { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" }, - { url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" }, - { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" }, - { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" }, - { url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" }, - { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" }, - { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, - { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, - { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, - { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, - { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, - { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, - { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, - { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, - { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, - { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, - { url = "https://files.pythonhosted.org/packages/63/d3/4a83af35d65e3fad632c926fad684c193ea4398569ccb0bbbc7fe8f5dc9a/cryptography-49.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b", size = 3993685, upload-time = "2026-06-12T20:02:14.883Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a7/f9dac0ab7f80368c56993a7bf638ef9935f825c91902798481fac0898138/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838", size = 4676239, upload-time = "2026-06-12T20:02:28.793Z" }, - { url = "https://files.pythonhosted.org/packages/d7/70/2ba3769dd0ae167e2f33dfa9592d45db6ff9a61d62ca1a5b3d1bdd09068f/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5", size = 4715584, upload-time = "2026-06-12T20:01:27.495Z" }, - { url = "https://files.pythonhosted.org/packages/94/64/2923570ac1c0bd3a737aa366ac3abbbbde273042308b8cde95e2364a6e6a/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615", size = 4675885, upload-time = "2026-06-12T20:01:55.49Z" }, - { url = "https://files.pythonhosted.org/packages/ab/f8/614dc7e051418cfe53d55173c1e24c6b0085e89996fe90508c2fdf769aef/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6", size = 4715449, upload-time = "2026-06-12T20:02:05.469Z" }, - { url = "https://files.pythonhosted.org/packages/aa/50/a9caea39ad19c431c1a3f8a31114df65b260cdfe67786b6c7e7c040c4c44/cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6", size = 3783731, upload-time = "2026-06-12T20:02:43.319Z" }, + { url = "https://files.pythonhosted.org/packages/e5/56/d194340cc4a57535e82e1bee9e89667ac4b7c13b5d3f59686deae3094dd5/cryptography-50.0.2-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:fa8f5efb344d6908a1ce62f4a24e2e5780f825d6f53f5f50ec5ffacac72936cb", size = 3914904, upload-time = "2026-09-30T14:43:44.339Z" }, + { url = "https://files.pythonhosted.org/packages/d9/69/c9bd862c3bf43d6399c433caf002df16e2dffd4be49bdf515cda38038711/cryptography-50.0.2-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:79def8d059362e7831389ed3be0ecdf58a89386e1271e35dd9f5af84e81bffd0", size = 4731146, upload-time = "2026-09-30T14:43:47.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/69/64cef1f702bf6657e0cc186ed1a2891d50d29fb41586b254e1c07adea261/cryptography-50.0.2-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:630ebfea3bf689d075f82316324ff7433dc447fe6bc1bfc76524b74b4a9567d2", size = 4719841, upload-time = "2026-09-30T14:43:49.01Z" }, + { url = "https://files.pythonhosted.org/packages/38/6b/61a3f8d8c5e1e49a6cddccafc4015cc1c0021360ab0acb4080e7a423644a/cryptography-50.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f9f6143a8c75945eb960d9eb98905a441394abfa24afaae239d514ffb2586480", size = 4738340, upload-time = "2026-09-30T14:43:50.932Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2e/7212ca32fd43dc91f2f41db20160b268098874b4c9a0e7be94d6835f5b2e/cryptography-50.0.2-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a582ab2ae1d34f67112cadc86702774c9ea4374df6bca6afe672817203c99134", size = 5367029, upload-time = "2026-09-30T14:43:52.911Z" }, + { url = "https://files.pythonhosted.org/packages/1a/f1/b474e930c4d910328780e3940da76f5aa5cbc48ce1fc14e44d239d9ea9db/cryptography-50.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4061c0079120205fb760c58acab6443e217307dcf05e3702cf970e0689972856", size = 4753050, upload-time = "2026-09-30T14:43:55.272Z" }, + { url = "https://files.pythonhosted.org/packages/7c/52/9af10e80ac16b0fcc2123f9cbd5e7afbd0fd5075bb7a607c592258a39cda/cryptography-50.0.2-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:ac9ed99d81760c62fe89d5f0815cdfa1ba9a35141cf30f1c2d044f04b4803d2e", size = 4376724, upload-time = "2026-09-30T14:43:57.24Z" }, + { url = "https://files.pythonhosted.org/packages/71/37/6202e488cc1eb625ea110c292c6bda92823176e023f427d8d5660ce8d632/cryptography-50.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:87e9ce85beb6b328ba370cc6e6aea483c92617b4c95b1d33a49297eb662bfb04", size = 4737859, upload-time = "2026-09-30T14:43:59.541Z" }, + { url = "https://files.pythonhosted.org/packages/8f/30/e86d7d518489b0ae2497091a35287abcb1a2ce4037837a34afbe9b1d6964/cryptography-50.0.2-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f265528741e048bce55c3463ed721fb0aa45a5888d8add8cfeccb3035451bbdc", size = 5324103, upload-time = "2026-09-30T14:44:01.901Z" }, + { url = "https://files.pythonhosted.org/packages/d3/69/2c833a049475e0a3444e94c7d0aca0aa51d166374a449b09e92ac98138de/cryptography-50.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dab55f57c74c3cad24c323bacbbd04be4705ba6eb0d92e920b1fc4837ed5079", size = 4752576, upload-time = "2026-09-30T14:44:04.545Z" }, + { url = "https://files.pythonhosted.org/packages/6c/5d/906970b83bbfc1f5bbfb677a143c181f2801f23b6a7204a3b47c42c97e65/cryptography-50.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25784ce8b9621c90c643efb9e1e2162ab3b0224cae446ad5e70e7fcb1ce18b51", size = 4870819, upload-time = "2026-09-30T14:44:06.884Z" }, + { url = "https://files.pythonhosted.org/packages/68/e3/f2298d3bb55e0c4a91841ec4d01b3f020ba8c5fbf15ccdcc6dcf03f97025/cryptography-50.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:85d0d9a31b9098e98534226d5686b47264b95e62ce459dc2e62fdfc809f9fe93", size = 5030152, upload-time = "2026-09-30T14:44:09.443Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4f/adfc442765721292fff86d314ce385d3249d22db42295c0dd057727b60f3/cryptography-50.0.2-cp311-abi3-win_amd64.whl", hash = "sha256:7afa5a6602a9f29af1f3a2965f831bae7c9d5d597b7cbb716d41ab3b7d89879c", size = 3824692, upload-time = "2026-09-30T14:44:11.671Z" }, + { url = "https://files.pythonhosted.org/packages/ce/cb/52eb3770c0d0be2702a98c6e96065ddc0a2877cf0845aa9c23397c142cd4/cryptography-50.0.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f785f6161f202ab04d8ca194158968798e480ca058943907972da5f12e2881e8", size = 3892731, upload-time = "2026-09-30T14:44:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/19/8e/aa1fc533d4546b127b45de8aa024eb5933d23eff9debfe25931e56861095/cryptography-50.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0ecbc5652bdb6fc9eaf89a7d196e20941adfe812f43bc4ca05d9150496821047", size = 4710431, upload-time = "2026-09-30T14:44:15.427Z" }, + { url = "https://files.pythonhosted.org/packages/6a/64/72bc3f75176e7e406b748a3e3830432b8c51297b38368713df04dc04898a/cryptography-50.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ab50ee449bf968271e820086f10a33d101dd060370abc10bcd22279be2656539", size = 4694824, upload-time = "2026-09-30T14:44:17.69Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c6/62c77550edfa5ca3f14bf44a1e6739b9fa09d6e998a11d97ed8213bccc98/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a9f7355e6fab51f6c369b86fb7571cffa05edee2c2121e0380a37fb9ac1cd5c1", size = 4716967, upload-time = "2026-09-30T14:44:19.661Z" }, + { url = "https://files.pythonhosted.org/packages/f4/37/cce70f150c432914460157a6ecc161752e053aa5ec0ef3b3f7dc6e31039a/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:94e5e9f108ee10471288214d3d233fbfbb492840a8457eb85178d643ddeb32c7", size = 5328676, upload-time = "2026-09-30T14:44:21.744Z" }, + { url = "https://files.pythonhosted.org/packages/aa/9a/6f2f0304d634ceafdeaf23e84537336664ac419b5d07611675c2ad3f6b7a/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:241449bf940a5d27309bd317e6f9a2af6932113818bb2b8f5c59ddc7ef16da18", size = 4727698, upload-time = "2026-09-30T14:44:24.178Z" }, + { url = "https://files.pythonhosted.org/packages/1d/de/66bcf9244d118663b2e1aaded8990f4640e3d7b7411870a5765f252074d2/cryptography-50.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d8947001be83df1394050758ce0e745dd74fb134eef0a4b5124208dfc3a68c37", size = 4354821, upload-time = "2026-09-30T14:44:26.263Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e6/db28a28c7b6c676addce89136de3d8db49ea825a8c863472e36e42ead4ad/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4a20ce1e5cb4284a86692fdcba7cb8754185c6b2e5c56fcef3751cf451d3cdc2", size = 4716748, upload-time = "2026-09-30T14:44:28.447Z" }, + { url = "https://files.pythonhosted.org/packages/30/96/01546c7f69ea0e2ab790a2e4f0934a4052fb9b388147fbf83c2fd72f1e57/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:84f964e537f916e2cc85199e5a88742e964939b575ac8598b3f9d6cc416cdaf1", size = 5285085, upload-time = "2026-09-30T14:44:30.704Z" }, + { url = "https://files.pythonhosted.org/packages/6c/01/03263395f74d50b071e9e66daace3f8bef80493e5d410726f2ba8554736b/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:828d49b0ff5a0e3975865571c5d91dbbdd0d38d8289b249a163e9425413a5e05", size = 4727268, upload-time = "2026-09-30T14:44:32.92Z" }, + { url = "https://files.pythonhosted.org/packages/eb/94/2bfe8f29ec0cc9c0d99359c4161adf32858e4934b72c6d100d2ac0bbe962/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:deb9fde5c60e437ee4821bc9bc39ff31b42135c27e1dc61ef0a629389c1de62e", size = 4849503, upload-time = "2026-09-30T14:44:34.969Z" }, + { url = "https://files.pythonhosted.org/packages/54/44/e80651ecbf0e42b62e2bb5f5768916e07eea72e1297338956a61df361f88/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8c71ba2cd31fc93748c38e1b613200ff1c2665cbfd5341fe3a61cfde35a1430e", size = 5004057, upload-time = "2026-09-30T14:44:37.064Z" }, + { url = "https://files.pythonhosted.org/packages/f8/cc/1d33befb3cd7ea7e77d2d73f43f2066471da1b21f24a6156efcaabf6d2e8/cryptography-50.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:78198641e5be9521beea5aa782bb551a58068d10e6eb04c9c680c1b69f2e7d45", size = 3795868, upload-time = "2026-09-30T14:44:39.71Z" }, + { url = "https://files.pythonhosted.org/packages/2d/49/93f6a6e7a87c9aa68d44d3e1cdb5fe8f60c90d5d2f46acae9a56892816b8/cryptography-50.0.2-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:edc3342adf8f697fc5f59c887a304356f147b397809440ed64e2fa6af2f50f37", size = 4133708, upload-time = "2026-09-30T14:44:41.807Z" }, + { url = "https://files.pythonhosted.org/packages/8c/75/32ac2a56243d778805c16ca6a32b8f74fb757df7e28d7ecb560afafb59cf/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d370b8d1dfcdf7130178137f6fbee6140774a1acc6cacefc4b42643ec11d0a3a", size = 4956267, upload-time = "2026-09-30T14:44:43.693Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a4/2c8d734e43d97f0842ee9f1b7b4bfb3d0cf5e19edebf43c2afe6675c2320/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2f9bd7f90c64fe89253f0a2c05e3c4856072660429ce8831b4235bf29403a67", size = 4966465, upload-time = "2026-09-30T14:44:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/c2/58/ee288c829a6f41f6235ae9dd33d82fd19b45442b65b4c8a3da36963d9f7a/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_aarch64.whl", hash = "sha256:e275096ea1e60cc595cda2836fd4a6c725d1125108b868be17f53684d164e2cc", size = 4959356, upload-time = "2026-09-30T14:44:48.211Z" }, + { url = "https://files.pythonhosted.org/packages/92/20/9ded6d51ddd9897f6b6e81fb9ebea7951d7cc5d6c890b0ed8abf77a51a80/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_ppc64le.whl", hash = "sha256:b13478603dcd0a2479ff8e87e2c19a7d525734686fe3c49542472293a204212d", size = 5548822, upload-time = "2026-09-30T14:44:50.86Z" }, + { url = "https://files.pythonhosted.org/packages/02/a8/8df951850d6b31d2a00218f19e2b3f999523437ed7a819df7fa427942fca/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_x86_64.whl", hash = "sha256:58a0c478eeca76fe5e07993c5a0703def34a6dc6a0cda4f5564639b33112ffe7", size = 5001199, upload-time = "2026-09-30T14:44:53.379Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f9/36b3022218ce75b7cdf068fb95f809f9bd0d820e4955ef43b90c255cc7ac/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_31_armv7l.whl", hash = "sha256:d38cdff612d06fa6a32840d5e1b1f7a27cee4a349aa9085d94a67789d6bfd408", size = 4629333, upload-time = "2026-09-30T14:44:55.635Z" }, + { url = "https://files.pythonhosted.org/packages/8c/72/20f99a219f6af47cdd1cbd978c243b92d71496e168a746138af44ded4f29/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_aarch64.whl", hash = "sha256:fdd28f912fccfec1846a94e2e1e8f9b0012f557f0c46fe4f3eb0d7a87afcf90b", size = 4958822, upload-time = "2026-09-30T14:44:59.639Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/196f112617fb08eb4d608a2a6c422373d46f9cc2857f38fc0667033c0899/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_ppc64le.whl", hash = "sha256:cbc8738fd8526d80f35cb3a40d41f41a2e7030bb3b18b09a6778ef63d291c2fd", size = 5506351, upload-time = "2026-09-30T14:45:02.267Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/83378121ef3eaaaf71d4b781577ff794acb39b9e1b87a3f156898c8497ed/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_x86_64.whl", hash = "sha256:e105ab60406787da31fccc883fc0f733af1efd78f0136a4599692c4083a73d0c", size = 5000859, upload-time = "2026-09-30T14:45:05.009Z" }, + { url = "https://files.pythonhosted.org/packages/22/f7/70fd7ae4d1dbfa7ba29b02e1b9068771519a86027756510b700ce81086a8/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:6f8700550aa1474a91e5dc07049c46f98b423b5b1ddd0483e0b51362eeeaf5be", size = 5092151, upload-time = "2026-09-30T15:29:15.932Z" }, + { url = "https://files.pythonhosted.org/packages/d4/be/688367b74de86984bd58d8efacfc7c9e68b89a6a22ced0fb4f38db50254a/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:c71be1cbfa5cd9a41ee452acf1eccd82b2c05950358b106ec8ceb83411d1a020", size = 5286120, upload-time = "2026-09-30T15:29:18.309Z" }, + { url = "https://files.pythonhosted.org/packages/39/d1/55f8a3f2ef5d1529e16835ef10cf0fe3d559ce237b46dddc440c0bba3649/cryptography-50.0.2-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:c423ab384a46c4dff7217b2ea5ba2e11cffdeab6441acd04cf65a369caf0366c", size = 4111557, upload-time = "2026-09-30T15:29:20.155Z" }, + { url = "https://files.pythonhosted.org/packages/23/ad/ac987755d00e1e64273760228d2635ae38dae2be83e3c6e0d3289d91dec3/cryptography-50.0.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:0ec5f09541743261e66e291b4a0cbf0fb2997aeaab6d9e9c740b9dba1b58d1c2", size = 3943588, upload-time = "2026-09-30T15:29:22.265Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8d/6d585339bedf85d45044c85d8412dac53f2bb6f918e8b7777efba1787844/cryptography-50.0.2-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5e67125c7dca78d199ec4e116aa93dbb83494808ecbb8211a2cb09b1bf41dbd", size = 4756166, upload-time = "2026-09-30T15:29:24.58Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f1/1c1f6874e8550cfddd4b688ceb38cefb6ed15ceed224d56f133f3d88c214/cryptography-50.0.2-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee247f5c245c9a2fe7c8e2214e295918838e44e00a45a6718451e4004219e767", size = 4749145, upload-time = "2026-09-30T15:29:26.807Z" }, + { url = "https://files.pythonhosted.org/packages/c1/63/61b15dc1a8de03fe0adbe3fd7608b3ad5c73bf50993bbcb1faaa930afe33/cryptography-50.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dfe9763530994147d9af1def057a5b9658b00e8f8fe8743d144d1e0911c2e454", size = 4763638, upload-time = "2026-09-30T15:29:28.588Z" }, + { url = "https://files.pythonhosted.org/packages/fc/35/b345bdfa40c9126df1a9d33236aa98418367931b8725f84fc3ae2b98dc59/cryptography-50.0.2-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:58ddb5a8e3179d12f19e4ea34d2d32e9d63a4baa142c875c1eb59f41b7243acd", size = 5382217, upload-time = "2026-09-30T15:29:30.589Z" }, + { url = "https://files.pythonhosted.org/packages/4f/87/ef344a9e616871f2519c22d6afcda79ddd5d35e9592d95eb6e677608d055/cryptography-50.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f21e8a22c8605750c7af886bab299a363721264061b4ac0a30efb73cfd58efc5", size = 4781387, upload-time = "2026-09-30T15:29:32.605Z" }, + { url = "https://files.pythonhosted.org/packages/90/5b/f2fdb13cd0b96f6f932c8627bb292a45f11c64d21620a8e120aee9a3b848/cryptography-50.0.2-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:9c8402a82ea0dc4ceeab793db05f0fafa8ca139ca34fcde5df0f596103c74107", size = 4403790, upload-time = "2026-09-30T15:29:34.374Z" }, + { url = "https://files.pythonhosted.org/packages/bc/ce/7e4f662b1e3c393513569e402cfc85ac7da0bd3d5435e122a3140219eb2d/cryptography-50.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:0ddc924c04591c2811ca024d62ecad4f7f6f08af8939c211438f48a16bd23602", size = 4764319, upload-time = "2026-09-30T15:29:36.149Z" }, + { url = "https://files.pythonhosted.org/packages/3c/3f/86ff33ce34cc0de6847fb96e035a1a760d81652e38643f617c02ad32ef7a/cryptography-50.0.2-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a6557e5f38e065ca9fbdaf7cfc7435ecb1d113aa81a022d1b51921ee7432e227", size = 5338560, upload-time = "2026-09-30T15:29:39.053Z" }, + { url = "https://files.pythonhosted.org/packages/40/cf/6b5c8e2fd9202d98988ab7cb5cc5c991704c4ad55f492ff408e4969f83f1/cryptography-50.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1981f1db4630889b9ef7803fadef12b056f428cb6b85c27ba57b774793b6093c", size = 4780973, upload-time = "2026-09-30T15:29:41.251Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/8d6ebc7dded797bd0f0160d52188021211f011a2b164ef0ae1dac4587465/cryptography-50.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a8701d6b584d76e909e3d305b7d126b41439876a5aaf76cddc67fc230eafa2e", size = 4897738, upload-time = "2026-09-30T15:29:43.106Z" }, + { url = "https://files.pythonhosted.org/packages/d4/aa/f3f6e0de7e6253b8baa8b2d8fb9d50924fa75cee3d4624bd4bc1208ee923/cryptography-50.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce47f66801c20ec6c6632453bb5960fe38939e9306970b48b3a5a26de7745d94", size = 5058280, upload-time = "2026-09-30T15:29:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b6/a1faf3a27ae9405fb34b1713cc73b2d8a26b04d5c561578fa2e6ef3e5bb9/cryptography-50.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:4e81d95e5bafc2d6e34e4bed780e53e4d5b9a2f928573428aa4d35fbec1eb0de", size = 3854095, upload-time = "2026-09-30T15:29:46.782Z" }, + { url = "https://files.pythonhosted.org/packages/1d/7a/f08d34ce09d60f89ebd391e2ebc6ba2b995e6dd7552f41820f8085f94e53/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:92e665960f25fcdc73725b9cec7a3824f279ba97a98653afe9ffac2e43668f67", size = 4716035, upload-time = "2026-09-30T15:29:48.681Z" }, + { url = "https://files.pythonhosted.org/packages/45/67/e18fb65592451a2acb76e9f2fbe14e0f47a8318b4c5430f1633851d03daa/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eef4c2f3423810b3070ab391f85436d2f8bbfcb286ac15cbc73190b3563b1f1a", size = 4726917, upload-time = "2026-09-30T15:29:50.608Z" }, + { url = "https://files.pythonhosted.org/packages/83/28/38fdce17e60f6b825e69fc3b7f75e70a6612759980704697e1de4cbfaf6e/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:7c6d0330c472d96f6a6afe24d80dfdf15176c33096f0a4397ae4c60f3dd3be48", size = 4715341, upload-time = "2026-09-30T15:29:52.522Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b1/d9121a717e0f893c64bd6ca7702614778d7df2a5c309128a002421788516/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:1ba34f04897fcdaa73f74145c25f3ec146fbd56593853e88adc2e811303c5f42", size = 4726322, upload-time = "2026-09-30T15:29:54.263Z" }, + { url = "https://files.pythonhosted.org/packages/36/8b/e6d153808bf353e152abd2fd4d8f09670d956ac78379ac46e60d7efbf04c/cryptography-50.0.2-pp311-pypy311_pp80-macosx_11_0_arm64.whl", hash = "sha256:3dc4fd8058cea1644971207d530e1a03a184a805ffc8ebdddf0599d78a331b81", size = 3873097, upload-time = "2026-09-30T15:29:56.097Z" }, + { url = "https://files.pythonhosted.org/packages/ca/1d/1271f287ff7170ddafc2aad36260c4eec20ccd2fea70f38455e9d56d427b/cryptography-50.0.2-pp311-pypy311_pp80-win_amd64.whl", hash = "sha256:7b75de3c8b3be1cdb1052747c929440c3eea46c1bc2cb8a6e3a48388e9b7b452", size = 3805376, upload-time = "2026-09-30T15:29:58.729Z" }, ] [[package]] diff --git a/src/git/uv.lock b/src/git/uv.lock index 821074839e..3921d8c8ed 100644 --- a/src/git/uv.lock +++ b/src/git/uv.lock @@ -288,59 +288,72 @@ toml = [ [[package]] name = "cryptography" -version = "49.0.0" +version = "50.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/af/182eb91b0df3fe75c4d9f26fe70684569566745f6ba7e5c9c73a862c5252/cryptography-50.0.2.tar.gz", hash = "sha256:7b46165bb56eb4704e2eaaf86f3c940d19154535d9b0ca7d6d590b04060e00d5", size = 880623, upload-time = "2026-09-30T15:30:04.884Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, - { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, - { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, - { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, - { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, - { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, - { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, - { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, - { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, - { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, - { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, - { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, - { url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" }, - { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" }, - { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" }, - { url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" }, - { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" }, - { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" }, - { url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" }, - { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" }, - { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, - { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, - { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, - { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, - { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, - { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, - { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, - { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, - { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, - { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, - { url = "https://files.pythonhosted.org/packages/63/d3/4a83af35d65e3fad632c926fad684c193ea4398569ccb0bbbc7fe8f5dc9a/cryptography-49.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b", size = 3993685, upload-time = "2026-06-12T20:02:14.883Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a7/f9dac0ab7f80368c56993a7bf638ef9935f825c91902798481fac0898138/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838", size = 4676239, upload-time = "2026-06-12T20:02:28.793Z" }, - { url = "https://files.pythonhosted.org/packages/d7/70/2ba3769dd0ae167e2f33dfa9592d45db6ff9a61d62ca1a5b3d1bdd09068f/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5", size = 4715584, upload-time = "2026-06-12T20:01:27.495Z" }, - { url = "https://files.pythonhosted.org/packages/94/64/2923570ac1c0bd3a737aa366ac3abbbbde273042308b8cde95e2364a6e6a/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615", size = 4675885, upload-time = "2026-06-12T20:01:55.49Z" }, - { url = "https://files.pythonhosted.org/packages/ab/f8/614dc7e051418cfe53d55173c1e24c6b0085e89996fe90508c2fdf769aef/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6", size = 4715449, upload-time = "2026-06-12T20:02:05.469Z" }, - { url = "https://files.pythonhosted.org/packages/aa/50/a9caea39ad19c431c1a3f8a31114df65b260cdfe67786b6c7e7c040c4c44/cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6", size = 3783731, upload-time = "2026-06-12T20:02:43.319Z" }, + { url = "https://files.pythonhosted.org/packages/e5/56/d194340cc4a57535e82e1bee9e89667ac4b7c13b5d3f59686deae3094dd5/cryptography-50.0.2-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:fa8f5efb344d6908a1ce62f4a24e2e5780f825d6f53f5f50ec5ffacac72936cb", size = 3914904, upload-time = "2026-09-30T14:43:44.339Z" }, + { url = "https://files.pythonhosted.org/packages/d9/69/c9bd862c3bf43d6399c433caf002df16e2dffd4be49bdf515cda38038711/cryptography-50.0.2-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:79def8d059362e7831389ed3be0ecdf58a89386e1271e35dd9f5af84e81bffd0", size = 4731146, upload-time = "2026-09-30T14:43:47.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/69/64cef1f702bf6657e0cc186ed1a2891d50d29fb41586b254e1c07adea261/cryptography-50.0.2-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:630ebfea3bf689d075f82316324ff7433dc447fe6bc1bfc76524b74b4a9567d2", size = 4719841, upload-time = "2026-09-30T14:43:49.01Z" }, + { url = "https://files.pythonhosted.org/packages/38/6b/61a3f8d8c5e1e49a6cddccafc4015cc1c0021360ab0acb4080e7a423644a/cryptography-50.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f9f6143a8c75945eb960d9eb98905a441394abfa24afaae239d514ffb2586480", size = 4738340, upload-time = "2026-09-30T14:43:50.932Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2e/7212ca32fd43dc91f2f41db20160b268098874b4c9a0e7be94d6835f5b2e/cryptography-50.0.2-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a582ab2ae1d34f67112cadc86702774c9ea4374df6bca6afe672817203c99134", size = 5367029, upload-time = "2026-09-30T14:43:52.911Z" }, + { url = "https://files.pythonhosted.org/packages/1a/f1/b474e930c4d910328780e3940da76f5aa5cbc48ce1fc14e44d239d9ea9db/cryptography-50.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4061c0079120205fb760c58acab6443e217307dcf05e3702cf970e0689972856", size = 4753050, upload-time = "2026-09-30T14:43:55.272Z" }, + { url = "https://files.pythonhosted.org/packages/7c/52/9af10e80ac16b0fcc2123f9cbd5e7afbd0fd5075bb7a607c592258a39cda/cryptography-50.0.2-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:ac9ed99d81760c62fe89d5f0815cdfa1ba9a35141cf30f1c2d044f04b4803d2e", size = 4376724, upload-time = "2026-09-30T14:43:57.24Z" }, + { url = "https://files.pythonhosted.org/packages/71/37/6202e488cc1eb625ea110c292c6bda92823176e023f427d8d5660ce8d632/cryptography-50.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:87e9ce85beb6b328ba370cc6e6aea483c92617b4c95b1d33a49297eb662bfb04", size = 4737859, upload-time = "2026-09-30T14:43:59.541Z" }, + { url = "https://files.pythonhosted.org/packages/8f/30/e86d7d518489b0ae2497091a35287abcb1a2ce4037837a34afbe9b1d6964/cryptography-50.0.2-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f265528741e048bce55c3463ed721fb0aa45a5888d8add8cfeccb3035451bbdc", size = 5324103, upload-time = "2026-09-30T14:44:01.901Z" }, + { url = "https://files.pythonhosted.org/packages/d3/69/2c833a049475e0a3444e94c7d0aca0aa51d166374a449b09e92ac98138de/cryptography-50.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dab55f57c74c3cad24c323bacbbd04be4705ba6eb0d92e920b1fc4837ed5079", size = 4752576, upload-time = "2026-09-30T14:44:04.545Z" }, + { url = "https://files.pythonhosted.org/packages/6c/5d/906970b83bbfc1f5bbfb677a143c181f2801f23b6a7204a3b47c42c97e65/cryptography-50.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25784ce8b9621c90c643efb9e1e2162ab3b0224cae446ad5e70e7fcb1ce18b51", size = 4870819, upload-time = "2026-09-30T14:44:06.884Z" }, + { url = "https://files.pythonhosted.org/packages/68/e3/f2298d3bb55e0c4a91841ec4d01b3f020ba8c5fbf15ccdcc6dcf03f97025/cryptography-50.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:85d0d9a31b9098e98534226d5686b47264b95e62ce459dc2e62fdfc809f9fe93", size = 5030152, upload-time = "2026-09-30T14:44:09.443Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4f/adfc442765721292fff86d314ce385d3249d22db42295c0dd057727b60f3/cryptography-50.0.2-cp311-abi3-win_amd64.whl", hash = "sha256:7afa5a6602a9f29af1f3a2965f831bae7c9d5d597b7cbb716d41ab3b7d89879c", size = 3824692, upload-time = "2026-09-30T14:44:11.671Z" }, + { url = "https://files.pythonhosted.org/packages/ce/cb/52eb3770c0d0be2702a98c6e96065ddc0a2877cf0845aa9c23397c142cd4/cryptography-50.0.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f785f6161f202ab04d8ca194158968798e480ca058943907972da5f12e2881e8", size = 3892731, upload-time = "2026-09-30T14:44:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/19/8e/aa1fc533d4546b127b45de8aa024eb5933d23eff9debfe25931e56861095/cryptography-50.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0ecbc5652bdb6fc9eaf89a7d196e20941adfe812f43bc4ca05d9150496821047", size = 4710431, upload-time = "2026-09-30T14:44:15.427Z" }, + { url = "https://files.pythonhosted.org/packages/6a/64/72bc3f75176e7e406b748a3e3830432b8c51297b38368713df04dc04898a/cryptography-50.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ab50ee449bf968271e820086f10a33d101dd060370abc10bcd22279be2656539", size = 4694824, upload-time = "2026-09-30T14:44:17.69Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c6/62c77550edfa5ca3f14bf44a1e6739b9fa09d6e998a11d97ed8213bccc98/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a9f7355e6fab51f6c369b86fb7571cffa05edee2c2121e0380a37fb9ac1cd5c1", size = 4716967, upload-time = "2026-09-30T14:44:19.661Z" }, + { url = "https://files.pythonhosted.org/packages/f4/37/cce70f150c432914460157a6ecc161752e053aa5ec0ef3b3f7dc6e31039a/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:94e5e9f108ee10471288214d3d233fbfbb492840a8457eb85178d643ddeb32c7", size = 5328676, upload-time = "2026-09-30T14:44:21.744Z" }, + { url = "https://files.pythonhosted.org/packages/aa/9a/6f2f0304d634ceafdeaf23e84537336664ac419b5d07611675c2ad3f6b7a/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:241449bf940a5d27309bd317e6f9a2af6932113818bb2b8f5c59ddc7ef16da18", size = 4727698, upload-time = "2026-09-30T14:44:24.178Z" }, + { url = "https://files.pythonhosted.org/packages/1d/de/66bcf9244d118663b2e1aaded8990f4640e3d7b7411870a5765f252074d2/cryptography-50.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d8947001be83df1394050758ce0e745dd74fb134eef0a4b5124208dfc3a68c37", size = 4354821, upload-time = "2026-09-30T14:44:26.263Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e6/db28a28c7b6c676addce89136de3d8db49ea825a8c863472e36e42ead4ad/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4a20ce1e5cb4284a86692fdcba7cb8754185c6b2e5c56fcef3751cf451d3cdc2", size = 4716748, upload-time = "2026-09-30T14:44:28.447Z" }, + { url = "https://files.pythonhosted.org/packages/30/96/01546c7f69ea0e2ab790a2e4f0934a4052fb9b388147fbf83c2fd72f1e57/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:84f964e537f916e2cc85199e5a88742e964939b575ac8598b3f9d6cc416cdaf1", size = 5285085, upload-time = "2026-09-30T14:44:30.704Z" }, + { url = "https://files.pythonhosted.org/packages/6c/01/03263395f74d50b071e9e66daace3f8bef80493e5d410726f2ba8554736b/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:828d49b0ff5a0e3975865571c5d91dbbdd0d38d8289b249a163e9425413a5e05", size = 4727268, upload-time = "2026-09-30T14:44:32.92Z" }, + { url = "https://files.pythonhosted.org/packages/eb/94/2bfe8f29ec0cc9c0d99359c4161adf32858e4934b72c6d100d2ac0bbe962/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:deb9fde5c60e437ee4821bc9bc39ff31b42135c27e1dc61ef0a629389c1de62e", size = 4849503, upload-time = "2026-09-30T14:44:34.969Z" }, + { url = "https://files.pythonhosted.org/packages/54/44/e80651ecbf0e42b62e2bb5f5768916e07eea72e1297338956a61df361f88/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8c71ba2cd31fc93748c38e1b613200ff1c2665cbfd5341fe3a61cfde35a1430e", size = 5004057, upload-time = "2026-09-30T14:44:37.064Z" }, + { url = "https://files.pythonhosted.org/packages/f8/cc/1d33befb3cd7ea7e77d2d73f43f2066471da1b21f24a6156efcaabf6d2e8/cryptography-50.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:78198641e5be9521beea5aa782bb551a58068d10e6eb04c9c680c1b69f2e7d45", size = 3795868, upload-time = "2026-09-30T14:44:39.71Z" }, + { url = "https://files.pythonhosted.org/packages/2d/49/93f6a6e7a87c9aa68d44d3e1cdb5fe8f60c90d5d2f46acae9a56892816b8/cryptography-50.0.2-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:edc3342adf8f697fc5f59c887a304356f147b397809440ed64e2fa6af2f50f37", size = 4133708, upload-time = "2026-09-30T14:44:41.807Z" }, + { url = "https://files.pythonhosted.org/packages/8c/75/32ac2a56243d778805c16ca6a32b8f74fb757df7e28d7ecb560afafb59cf/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d370b8d1dfcdf7130178137f6fbee6140774a1acc6cacefc4b42643ec11d0a3a", size = 4956267, upload-time = "2026-09-30T14:44:43.693Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a4/2c8d734e43d97f0842ee9f1b7b4bfb3d0cf5e19edebf43c2afe6675c2320/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2f9bd7f90c64fe89253f0a2c05e3c4856072660429ce8831b4235bf29403a67", size = 4966465, upload-time = "2026-09-30T14:44:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/c2/58/ee288c829a6f41f6235ae9dd33d82fd19b45442b65b4c8a3da36963d9f7a/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_aarch64.whl", hash = "sha256:e275096ea1e60cc595cda2836fd4a6c725d1125108b868be17f53684d164e2cc", size = 4959356, upload-time = "2026-09-30T14:44:48.211Z" }, + { url = "https://files.pythonhosted.org/packages/92/20/9ded6d51ddd9897f6b6e81fb9ebea7951d7cc5d6c890b0ed8abf77a51a80/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_ppc64le.whl", hash = "sha256:b13478603dcd0a2479ff8e87e2c19a7d525734686fe3c49542472293a204212d", size = 5548822, upload-time = "2026-09-30T14:44:50.86Z" }, + { url = "https://files.pythonhosted.org/packages/02/a8/8df951850d6b31d2a00218f19e2b3f999523437ed7a819df7fa427942fca/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_x86_64.whl", hash = "sha256:58a0c478eeca76fe5e07993c5a0703def34a6dc6a0cda4f5564639b33112ffe7", size = 5001199, upload-time = "2026-09-30T14:44:53.379Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f9/36b3022218ce75b7cdf068fb95f809f9bd0d820e4955ef43b90c255cc7ac/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_31_armv7l.whl", hash = "sha256:d38cdff612d06fa6a32840d5e1b1f7a27cee4a349aa9085d94a67789d6bfd408", size = 4629333, upload-time = "2026-09-30T14:44:55.635Z" }, + { url = "https://files.pythonhosted.org/packages/8c/72/20f99a219f6af47cdd1cbd978c243b92d71496e168a746138af44ded4f29/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_aarch64.whl", hash = "sha256:fdd28f912fccfec1846a94e2e1e8f9b0012f557f0c46fe4f3eb0d7a87afcf90b", size = 4958822, upload-time = "2026-09-30T14:44:59.639Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/196f112617fb08eb4d608a2a6c422373d46f9cc2857f38fc0667033c0899/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_ppc64le.whl", hash = "sha256:cbc8738fd8526d80f35cb3a40d41f41a2e7030bb3b18b09a6778ef63d291c2fd", size = 5506351, upload-time = "2026-09-30T14:45:02.267Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/83378121ef3eaaaf71d4b781577ff794acb39b9e1b87a3f156898c8497ed/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_x86_64.whl", hash = "sha256:e105ab60406787da31fccc883fc0f733af1efd78f0136a4599692c4083a73d0c", size = 5000859, upload-time = "2026-09-30T14:45:05.009Z" }, + { url = "https://files.pythonhosted.org/packages/22/f7/70fd7ae4d1dbfa7ba29b02e1b9068771519a86027756510b700ce81086a8/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:6f8700550aa1474a91e5dc07049c46f98b423b5b1ddd0483e0b51362eeeaf5be", size = 5092151, upload-time = "2026-09-30T15:29:15.932Z" }, + { url = "https://files.pythonhosted.org/packages/d4/be/688367b74de86984bd58d8efacfc7c9e68b89a6a22ced0fb4f38db50254a/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:c71be1cbfa5cd9a41ee452acf1eccd82b2c05950358b106ec8ceb83411d1a020", size = 5286120, upload-time = "2026-09-30T15:29:18.309Z" }, + { url = "https://files.pythonhosted.org/packages/39/d1/55f8a3f2ef5d1529e16835ef10cf0fe3d559ce237b46dddc440c0bba3649/cryptography-50.0.2-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:c423ab384a46c4dff7217b2ea5ba2e11cffdeab6441acd04cf65a369caf0366c", size = 4111557, upload-time = "2026-09-30T15:29:20.155Z" }, + { url = "https://files.pythonhosted.org/packages/23/ad/ac987755d00e1e64273760228d2635ae38dae2be83e3c6e0d3289d91dec3/cryptography-50.0.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:0ec5f09541743261e66e291b4a0cbf0fb2997aeaab6d9e9c740b9dba1b58d1c2", size = 3943588, upload-time = "2026-09-30T15:29:22.265Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8d/6d585339bedf85d45044c85d8412dac53f2bb6f918e8b7777efba1787844/cryptography-50.0.2-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5e67125c7dca78d199ec4e116aa93dbb83494808ecbb8211a2cb09b1bf41dbd", size = 4756166, upload-time = "2026-09-30T15:29:24.58Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f1/1c1f6874e8550cfddd4b688ceb38cefb6ed15ceed224d56f133f3d88c214/cryptography-50.0.2-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee247f5c245c9a2fe7c8e2214e295918838e44e00a45a6718451e4004219e767", size = 4749145, upload-time = "2026-09-30T15:29:26.807Z" }, + { url = "https://files.pythonhosted.org/packages/c1/63/61b15dc1a8de03fe0adbe3fd7608b3ad5c73bf50993bbcb1faaa930afe33/cryptography-50.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dfe9763530994147d9af1def057a5b9658b00e8f8fe8743d144d1e0911c2e454", size = 4763638, upload-time = "2026-09-30T15:29:28.588Z" }, + { url = "https://files.pythonhosted.org/packages/fc/35/b345bdfa40c9126df1a9d33236aa98418367931b8725f84fc3ae2b98dc59/cryptography-50.0.2-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:58ddb5a8e3179d12f19e4ea34d2d32e9d63a4baa142c875c1eb59f41b7243acd", size = 5382217, upload-time = "2026-09-30T15:29:30.589Z" }, + { url = "https://files.pythonhosted.org/packages/4f/87/ef344a9e616871f2519c22d6afcda79ddd5d35e9592d95eb6e677608d055/cryptography-50.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f21e8a22c8605750c7af886bab299a363721264061b4ac0a30efb73cfd58efc5", size = 4781387, upload-time = "2026-09-30T15:29:32.605Z" }, + { url = "https://files.pythonhosted.org/packages/90/5b/f2fdb13cd0b96f6f932c8627bb292a45f11c64d21620a8e120aee9a3b848/cryptography-50.0.2-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:9c8402a82ea0dc4ceeab793db05f0fafa8ca139ca34fcde5df0f596103c74107", size = 4403790, upload-time = "2026-09-30T15:29:34.374Z" }, + { url = "https://files.pythonhosted.org/packages/bc/ce/7e4f662b1e3c393513569e402cfc85ac7da0bd3d5435e122a3140219eb2d/cryptography-50.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:0ddc924c04591c2811ca024d62ecad4f7f6f08af8939c211438f48a16bd23602", size = 4764319, upload-time = "2026-09-30T15:29:36.149Z" }, + { url = "https://files.pythonhosted.org/packages/3c/3f/86ff33ce34cc0de6847fb96e035a1a760d81652e38643f617c02ad32ef7a/cryptography-50.0.2-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a6557e5f38e065ca9fbdaf7cfc7435ecb1d113aa81a022d1b51921ee7432e227", size = 5338560, upload-time = "2026-09-30T15:29:39.053Z" }, + { url = "https://files.pythonhosted.org/packages/40/cf/6b5c8e2fd9202d98988ab7cb5cc5c991704c4ad55f492ff408e4969f83f1/cryptography-50.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1981f1db4630889b9ef7803fadef12b056f428cb6b85c27ba57b774793b6093c", size = 4780973, upload-time = "2026-09-30T15:29:41.251Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/8d6ebc7dded797bd0f0160d52188021211f011a2b164ef0ae1dac4587465/cryptography-50.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a8701d6b584d76e909e3d305b7d126b41439876a5aaf76cddc67fc230eafa2e", size = 4897738, upload-time = "2026-09-30T15:29:43.106Z" }, + { url = "https://files.pythonhosted.org/packages/d4/aa/f3f6e0de7e6253b8baa8b2d8fb9d50924fa75cee3d4624bd4bc1208ee923/cryptography-50.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce47f66801c20ec6c6632453bb5960fe38939e9306970b48b3a5a26de7745d94", size = 5058280, upload-time = "2026-09-30T15:29:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b6/a1faf3a27ae9405fb34b1713cc73b2d8a26b04d5c561578fa2e6ef3e5bb9/cryptography-50.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:4e81d95e5bafc2d6e34e4bed780e53e4d5b9a2f928573428aa4d35fbec1eb0de", size = 3854095, upload-time = "2026-09-30T15:29:46.782Z" }, + { url = "https://files.pythonhosted.org/packages/1d/7a/f08d34ce09d60f89ebd391e2ebc6ba2b995e6dd7552f41820f8085f94e53/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:92e665960f25fcdc73725b9cec7a3824f279ba97a98653afe9ffac2e43668f67", size = 4716035, upload-time = "2026-09-30T15:29:48.681Z" }, + { url = "https://files.pythonhosted.org/packages/45/67/e18fb65592451a2acb76e9f2fbe14e0f47a8318b4c5430f1633851d03daa/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eef4c2f3423810b3070ab391f85436d2f8bbfcb286ac15cbc73190b3563b1f1a", size = 4726917, upload-time = "2026-09-30T15:29:50.608Z" }, + { url = "https://files.pythonhosted.org/packages/83/28/38fdce17e60f6b825e69fc3b7f75e70a6612759980704697e1de4cbfaf6e/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:7c6d0330c472d96f6a6afe24d80dfdf15176c33096f0a4397ae4c60f3dd3be48", size = 4715341, upload-time = "2026-09-30T15:29:52.522Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b1/d9121a717e0f893c64bd6ca7702614778d7df2a5c309128a002421788516/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:1ba34f04897fcdaa73f74145c25f3ec146fbd56593853e88adc2e811303c5f42", size = 4726322, upload-time = "2026-09-30T15:29:54.263Z" }, + { url = "https://files.pythonhosted.org/packages/36/8b/e6d153808bf353e152abd2fd4d8f09670d956ac78379ac46e60d7efbf04c/cryptography-50.0.2-pp311-pypy311_pp80-macosx_11_0_arm64.whl", hash = "sha256:3dc4fd8058cea1644971207d530e1a03a184a805ffc8ebdddf0599d78a331b81", size = 3873097, upload-time = "2026-09-30T15:29:56.097Z" }, + { url = "https://files.pythonhosted.org/packages/ca/1d/1271f287ff7170ddafc2aad36260c4eec20ccd2fea70f38455e9d56d427b/cryptography-50.0.2-pp311-pypy311_pp80-win_amd64.whl", hash = "sha256:7b75de3c8b3be1cdb1052747c929440c3eea46c1bc2cb8a6e3a48388e9b7b452", size = 3805376, upload-time = "2026-09-30T15:29:58.729Z" }, ] [[package]] diff --git a/src/time/uv.lock b/src/time/uv.lock index d5201d4788..93cde3dd7e 100644 --- a/src/time/uv.lock +++ b/src/time/uv.lock @@ -288,59 +288,72 @@ toml = [ [[package]] name = "cryptography" -version = "49.0.0" +version = "50.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/af/182eb91b0df3fe75c4d9f26fe70684569566745f6ba7e5c9c73a862c5252/cryptography-50.0.2.tar.gz", hash = "sha256:7b46165bb56eb4704e2eaaf86f3c940d19154535d9b0ca7d6d590b04060e00d5", size = 880623, upload-time = "2026-09-30T15:30:04.884Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, - { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, - { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, - { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, - { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, - { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, - { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, - { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, - { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, - { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, - { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, - { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, - { url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" }, - { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" }, - { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" }, - { url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" }, - { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" }, - { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" }, - { url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" }, - { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" }, - { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, - { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, - { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, - { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, - { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, - { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, - { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, - { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, - { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, - { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, - { url = "https://files.pythonhosted.org/packages/63/d3/4a83af35d65e3fad632c926fad684c193ea4398569ccb0bbbc7fe8f5dc9a/cryptography-49.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b", size = 3993685, upload-time = "2026-06-12T20:02:14.883Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a7/f9dac0ab7f80368c56993a7bf638ef9935f825c91902798481fac0898138/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838", size = 4676239, upload-time = "2026-06-12T20:02:28.793Z" }, - { url = "https://files.pythonhosted.org/packages/d7/70/2ba3769dd0ae167e2f33dfa9592d45db6ff9a61d62ca1a5b3d1bdd09068f/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5", size = 4715584, upload-time = "2026-06-12T20:01:27.495Z" }, - { url = "https://files.pythonhosted.org/packages/94/64/2923570ac1c0bd3a737aa366ac3abbbbde273042308b8cde95e2364a6e6a/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615", size = 4675885, upload-time = "2026-06-12T20:01:55.49Z" }, - { url = "https://files.pythonhosted.org/packages/ab/f8/614dc7e051418cfe53d55173c1e24c6b0085e89996fe90508c2fdf769aef/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6", size = 4715449, upload-time = "2026-06-12T20:02:05.469Z" }, - { url = "https://files.pythonhosted.org/packages/aa/50/a9caea39ad19c431c1a3f8a31114df65b260cdfe67786b6c7e7c040c4c44/cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6", size = 3783731, upload-time = "2026-06-12T20:02:43.319Z" }, + { url = "https://files.pythonhosted.org/packages/e5/56/d194340cc4a57535e82e1bee9e89667ac4b7c13b5d3f59686deae3094dd5/cryptography-50.0.2-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:fa8f5efb344d6908a1ce62f4a24e2e5780f825d6f53f5f50ec5ffacac72936cb", size = 3914904, upload-time = "2026-09-30T14:43:44.339Z" }, + { url = "https://files.pythonhosted.org/packages/d9/69/c9bd862c3bf43d6399c433caf002df16e2dffd4be49bdf515cda38038711/cryptography-50.0.2-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:79def8d059362e7831389ed3be0ecdf58a89386e1271e35dd9f5af84e81bffd0", size = 4731146, upload-time = "2026-09-30T14:43:47.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/69/64cef1f702bf6657e0cc186ed1a2891d50d29fb41586b254e1c07adea261/cryptography-50.0.2-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:630ebfea3bf689d075f82316324ff7433dc447fe6bc1bfc76524b74b4a9567d2", size = 4719841, upload-time = "2026-09-30T14:43:49.01Z" }, + { url = "https://files.pythonhosted.org/packages/38/6b/61a3f8d8c5e1e49a6cddccafc4015cc1c0021360ab0acb4080e7a423644a/cryptography-50.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f9f6143a8c75945eb960d9eb98905a441394abfa24afaae239d514ffb2586480", size = 4738340, upload-time = "2026-09-30T14:43:50.932Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2e/7212ca32fd43dc91f2f41db20160b268098874b4c9a0e7be94d6835f5b2e/cryptography-50.0.2-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a582ab2ae1d34f67112cadc86702774c9ea4374df6bca6afe672817203c99134", size = 5367029, upload-time = "2026-09-30T14:43:52.911Z" }, + { url = "https://files.pythonhosted.org/packages/1a/f1/b474e930c4d910328780e3940da76f5aa5cbc48ce1fc14e44d239d9ea9db/cryptography-50.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4061c0079120205fb760c58acab6443e217307dcf05e3702cf970e0689972856", size = 4753050, upload-time = "2026-09-30T14:43:55.272Z" }, + { url = "https://files.pythonhosted.org/packages/7c/52/9af10e80ac16b0fcc2123f9cbd5e7afbd0fd5075bb7a607c592258a39cda/cryptography-50.0.2-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:ac9ed99d81760c62fe89d5f0815cdfa1ba9a35141cf30f1c2d044f04b4803d2e", size = 4376724, upload-time = "2026-09-30T14:43:57.24Z" }, + { url = "https://files.pythonhosted.org/packages/71/37/6202e488cc1eb625ea110c292c6bda92823176e023f427d8d5660ce8d632/cryptography-50.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:87e9ce85beb6b328ba370cc6e6aea483c92617b4c95b1d33a49297eb662bfb04", size = 4737859, upload-time = "2026-09-30T14:43:59.541Z" }, + { url = "https://files.pythonhosted.org/packages/8f/30/e86d7d518489b0ae2497091a35287abcb1a2ce4037837a34afbe9b1d6964/cryptography-50.0.2-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f265528741e048bce55c3463ed721fb0aa45a5888d8add8cfeccb3035451bbdc", size = 5324103, upload-time = "2026-09-30T14:44:01.901Z" }, + { url = "https://files.pythonhosted.org/packages/d3/69/2c833a049475e0a3444e94c7d0aca0aa51d166374a449b09e92ac98138de/cryptography-50.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dab55f57c74c3cad24c323bacbbd04be4705ba6eb0d92e920b1fc4837ed5079", size = 4752576, upload-time = "2026-09-30T14:44:04.545Z" }, + { url = "https://files.pythonhosted.org/packages/6c/5d/906970b83bbfc1f5bbfb677a143c181f2801f23b6a7204a3b47c42c97e65/cryptography-50.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25784ce8b9621c90c643efb9e1e2162ab3b0224cae446ad5e70e7fcb1ce18b51", size = 4870819, upload-time = "2026-09-30T14:44:06.884Z" }, + { url = "https://files.pythonhosted.org/packages/68/e3/f2298d3bb55e0c4a91841ec4d01b3f020ba8c5fbf15ccdcc6dcf03f97025/cryptography-50.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:85d0d9a31b9098e98534226d5686b47264b95e62ce459dc2e62fdfc809f9fe93", size = 5030152, upload-time = "2026-09-30T14:44:09.443Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4f/adfc442765721292fff86d314ce385d3249d22db42295c0dd057727b60f3/cryptography-50.0.2-cp311-abi3-win_amd64.whl", hash = "sha256:7afa5a6602a9f29af1f3a2965f831bae7c9d5d597b7cbb716d41ab3b7d89879c", size = 3824692, upload-time = "2026-09-30T14:44:11.671Z" }, + { url = "https://files.pythonhosted.org/packages/ce/cb/52eb3770c0d0be2702a98c6e96065ddc0a2877cf0845aa9c23397c142cd4/cryptography-50.0.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f785f6161f202ab04d8ca194158968798e480ca058943907972da5f12e2881e8", size = 3892731, upload-time = "2026-09-30T14:44:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/19/8e/aa1fc533d4546b127b45de8aa024eb5933d23eff9debfe25931e56861095/cryptography-50.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0ecbc5652bdb6fc9eaf89a7d196e20941adfe812f43bc4ca05d9150496821047", size = 4710431, upload-time = "2026-09-30T14:44:15.427Z" }, + { url = "https://files.pythonhosted.org/packages/6a/64/72bc3f75176e7e406b748a3e3830432b8c51297b38368713df04dc04898a/cryptography-50.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ab50ee449bf968271e820086f10a33d101dd060370abc10bcd22279be2656539", size = 4694824, upload-time = "2026-09-30T14:44:17.69Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c6/62c77550edfa5ca3f14bf44a1e6739b9fa09d6e998a11d97ed8213bccc98/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a9f7355e6fab51f6c369b86fb7571cffa05edee2c2121e0380a37fb9ac1cd5c1", size = 4716967, upload-time = "2026-09-30T14:44:19.661Z" }, + { url = "https://files.pythonhosted.org/packages/f4/37/cce70f150c432914460157a6ecc161752e053aa5ec0ef3b3f7dc6e31039a/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:94e5e9f108ee10471288214d3d233fbfbb492840a8457eb85178d643ddeb32c7", size = 5328676, upload-time = "2026-09-30T14:44:21.744Z" }, + { url = "https://files.pythonhosted.org/packages/aa/9a/6f2f0304d634ceafdeaf23e84537336664ac419b5d07611675c2ad3f6b7a/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:241449bf940a5d27309bd317e6f9a2af6932113818bb2b8f5c59ddc7ef16da18", size = 4727698, upload-time = "2026-09-30T14:44:24.178Z" }, + { url = "https://files.pythonhosted.org/packages/1d/de/66bcf9244d118663b2e1aaded8990f4640e3d7b7411870a5765f252074d2/cryptography-50.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d8947001be83df1394050758ce0e745dd74fb134eef0a4b5124208dfc3a68c37", size = 4354821, upload-time = "2026-09-30T14:44:26.263Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e6/db28a28c7b6c676addce89136de3d8db49ea825a8c863472e36e42ead4ad/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4a20ce1e5cb4284a86692fdcba7cb8754185c6b2e5c56fcef3751cf451d3cdc2", size = 4716748, upload-time = "2026-09-30T14:44:28.447Z" }, + { url = "https://files.pythonhosted.org/packages/30/96/01546c7f69ea0e2ab790a2e4f0934a4052fb9b388147fbf83c2fd72f1e57/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:84f964e537f916e2cc85199e5a88742e964939b575ac8598b3f9d6cc416cdaf1", size = 5285085, upload-time = "2026-09-30T14:44:30.704Z" }, + { url = "https://files.pythonhosted.org/packages/6c/01/03263395f74d50b071e9e66daace3f8bef80493e5d410726f2ba8554736b/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:828d49b0ff5a0e3975865571c5d91dbbdd0d38d8289b249a163e9425413a5e05", size = 4727268, upload-time = "2026-09-30T14:44:32.92Z" }, + { url = "https://files.pythonhosted.org/packages/eb/94/2bfe8f29ec0cc9c0d99359c4161adf32858e4934b72c6d100d2ac0bbe962/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:deb9fde5c60e437ee4821bc9bc39ff31b42135c27e1dc61ef0a629389c1de62e", size = 4849503, upload-time = "2026-09-30T14:44:34.969Z" }, + { url = "https://files.pythonhosted.org/packages/54/44/e80651ecbf0e42b62e2bb5f5768916e07eea72e1297338956a61df361f88/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8c71ba2cd31fc93748c38e1b613200ff1c2665cbfd5341fe3a61cfde35a1430e", size = 5004057, upload-time = "2026-09-30T14:44:37.064Z" }, + { url = "https://files.pythonhosted.org/packages/f8/cc/1d33befb3cd7ea7e77d2d73f43f2066471da1b21f24a6156efcaabf6d2e8/cryptography-50.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:78198641e5be9521beea5aa782bb551a58068d10e6eb04c9c680c1b69f2e7d45", size = 3795868, upload-time = "2026-09-30T14:44:39.71Z" }, + { url = "https://files.pythonhosted.org/packages/2d/49/93f6a6e7a87c9aa68d44d3e1cdb5fe8f60c90d5d2f46acae9a56892816b8/cryptography-50.0.2-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:edc3342adf8f697fc5f59c887a304356f147b397809440ed64e2fa6af2f50f37", size = 4133708, upload-time = "2026-09-30T14:44:41.807Z" }, + { url = "https://files.pythonhosted.org/packages/8c/75/32ac2a56243d778805c16ca6a32b8f74fb757df7e28d7ecb560afafb59cf/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d370b8d1dfcdf7130178137f6fbee6140774a1acc6cacefc4b42643ec11d0a3a", size = 4956267, upload-time = "2026-09-30T14:44:43.693Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a4/2c8d734e43d97f0842ee9f1b7b4bfb3d0cf5e19edebf43c2afe6675c2320/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2f9bd7f90c64fe89253f0a2c05e3c4856072660429ce8831b4235bf29403a67", size = 4966465, upload-time = "2026-09-30T14:44:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/c2/58/ee288c829a6f41f6235ae9dd33d82fd19b45442b65b4c8a3da36963d9f7a/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_aarch64.whl", hash = "sha256:e275096ea1e60cc595cda2836fd4a6c725d1125108b868be17f53684d164e2cc", size = 4959356, upload-time = "2026-09-30T14:44:48.211Z" }, + { url = "https://files.pythonhosted.org/packages/92/20/9ded6d51ddd9897f6b6e81fb9ebea7951d7cc5d6c890b0ed8abf77a51a80/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_ppc64le.whl", hash = "sha256:b13478603dcd0a2479ff8e87e2c19a7d525734686fe3c49542472293a204212d", size = 5548822, upload-time = "2026-09-30T14:44:50.86Z" }, + { url = "https://files.pythonhosted.org/packages/02/a8/8df951850d6b31d2a00218f19e2b3f999523437ed7a819df7fa427942fca/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_x86_64.whl", hash = "sha256:58a0c478eeca76fe5e07993c5a0703def34a6dc6a0cda4f5564639b33112ffe7", size = 5001199, upload-time = "2026-09-30T14:44:53.379Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f9/36b3022218ce75b7cdf068fb95f809f9bd0d820e4955ef43b90c255cc7ac/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_31_armv7l.whl", hash = "sha256:d38cdff612d06fa6a32840d5e1b1f7a27cee4a349aa9085d94a67789d6bfd408", size = 4629333, upload-time = "2026-09-30T14:44:55.635Z" }, + { url = "https://files.pythonhosted.org/packages/8c/72/20f99a219f6af47cdd1cbd978c243b92d71496e168a746138af44ded4f29/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_aarch64.whl", hash = "sha256:fdd28f912fccfec1846a94e2e1e8f9b0012f557f0c46fe4f3eb0d7a87afcf90b", size = 4958822, upload-time = "2026-09-30T14:44:59.639Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/196f112617fb08eb4d608a2a6c422373d46f9cc2857f38fc0667033c0899/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_ppc64le.whl", hash = "sha256:cbc8738fd8526d80f35cb3a40d41f41a2e7030bb3b18b09a6778ef63d291c2fd", size = 5506351, upload-time = "2026-09-30T14:45:02.267Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/83378121ef3eaaaf71d4b781577ff794acb39b9e1b87a3f156898c8497ed/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_x86_64.whl", hash = "sha256:e105ab60406787da31fccc883fc0f733af1efd78f0136a4599692c4083a73d0c", size = 5000859, upload-time = "2026-09-30T14:45:05.009Z" }, + { url = "https://files.pythonhosted.org/packages/22/f7/70fd7ae4d1dbfa7ba29b02e1b9068771519a86027756510b700ce81086a8/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:6f8700550aa1474a91e5dc07049c46f98b423b5b1ddd0483e0b51362eeeaf5be", size = 5092151, upload-time = "2026-09-30T15:29:15.932Z" }, + { url = "https://files.pythonhosted.org/packages/d4/be/688367b74de86984bd58d8efacfc7c9e68b89a6a22ced0fb4f38db50254a/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:c71be1cbfa5cd9a41ee452acf1eccd82b2c05950358b106ec8ceb83411d1a020", size = 5286120, upload-time = "2026-09-30T15:29:18.309Z" }, + { url = "https://files.pythonhosted.org/packages/39/d1/55f8a3f2ef5d1529e16835ef10cf0fe3d559ce237b46dddc440c0bba3649/cryptography-50.0.2-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:c423ab384a46c4dff7217b2ea5ba2e11cffdeab6441acd04cf65a369caf0366c", size = 4111557, upload-time = "2026-09-30T15:29:20.155Z" }, + { url = "https://files.pythonhosted.org/packages/23/ad/ac987755d00e1e64273760228d2635ae38dae2be83e3c6e0d3289d91dec3/cryptography-50.0.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:0ec5f09541743261e66e291b4a0cbf0fb2997aeaab6d9e9c740b9dba1b58d1c2", size = 3943588, upload-time = "2026-09-30T15:29:22.265Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8d/6d585339bedf85d45044c85d8412dac53f2bb6f918e8b7777efba1787844/cryptography-50.0.2-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5e67125c7dca78d199ec4e116aa93dbb83494808ecbb8211a2cb09b1bf41dbd", size = 4756166, upload-time = "2026-09-30T15:29:24.58Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f1/1c1f6874e8550cfddd4b688ceb38cefb6ed15ceed224d56f133f3d88c214/cryptography-50.0.2-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee247f5c245c9a2fe7c8e2214e295918838e44e00a45a6718451e4004219e767", size = 4749145, upload-time = "2026-09-30T15:29:26.807Z" }, + { url = "https://files.pythonhosted.org/packages/c1/63/61b15dc1a8de03fe0adbe3fd7608b3ad5c73bf50993bbcb1faaa930afe33/cryptography-50.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dfe9763530994147d9af1def057a5b9658b00e8f8fe8743d144d1e0911c2e454", size = 4763638, upload-time = "2026-09-30T15:29:28.588Z" }, + { url = "https://files.pythonhosted.org/packages/fc/35/b345bdfa40c9126df1a9d33236aa98418367931b8725f84fc3ae2b98dc59/cryptography-50.0.2-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:58ddb5a8e3179d12f19e4ea34d2d32e9d63a4baa142c875c1eb59f41b7243acd", size = 5382217, upload-time = "2026-09-30T15:29:30.589Z" }, + { url = "https://files.pythonhosted.org/packages/4f/87/ef344a9e616871f2519c22d6afcda79ddd5d35e9592d95eb6e677608d055/cryptography-50.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f21e8a22c8605750c7af886bab299a363721264061b4ac0a30efb73cfd58efc5", size = 4781387, upload-time = "2026-09-30T15:29:32.605Z" }, + { url = "https://files.pythonhosted.org/packages/90/5b/f2fdb13cd0b96f6f932c8627bb292a45f11c64d21620a8e120aee9a3b848/cryptography-50.0.2-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:9c8402a82ea0dc4ceeab793db05f0fafa8ca139ca34fcde5df0f596103c74107", size = 4403790, upload-time = "2026-09-30T15:29:34.374Z" }, + { url = "https://files.pythonhosted.org/packages/bc/ce/7e4f662b1e3c393513569e402cfc85ac7da0bd3d5435e122a3140219eb2d/cryptography-50.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:0ddc924c04591c2811ca024d62ecad4f7f6f08af8939c211438f48a16bd23602", size = 4764319, upload-time = "2026-09-30T15:29:36.149Z" }, + { url = "https://files.pythonhosted.org/packages/3c/3f/86ff33ce34cc0de6847fb96e035a1a760d81652e38643f617c02ad32ef7a/cryptography-50.0.2-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a6557e5f38e065ca9fbdaf7cfc7435ecb1d113aa81a022d1b51921ee7432e227", size = 5338560, upload-time = "2026-09-30T15:29:39.053Z" }, + { url = "https://files.pythonhosted.org/packages/40/cf/6b5c8e2fd9202d98988ab7cb5cc5c991704c4ad55f492ff408e4969f83f1/cryptography-50.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1981f1db4630889b9ef7803fadef12b056f428cb6b85c27ba57b774793b6093c", size = 4780973, upload-time = "2026-09-30T15:29:41.251Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/8d6ebc7dded797bd0f0160d52188021211f011a2b164ef0ae1dac4587465/cryptography-50.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a8701d6b584d76e909e3d305b7d126b41439876a5aaf76cddc67fc230eafa2e", size = 4897738, upload-time = "2026-09-30T15:29:43.106Z" }, + { url = "https://files.pythonhosted.org/packages/d4/aa/f3f6e0de7e6253b8baa8b2d8fb9d50924fa75cee3d4624bd4bc1208ee923/cryptography-50.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce47f66801c20ec6c6632453bb5960fe38939e9306970b48b3a5a26de7745d94", size = 5058280, upload-time = "2026-09-30T15:29:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b6/a1faf3a27ae9405fb34b1713cc73b2d8a26b04d5c561578fa2e6ef3e5bb9/cryptography-50.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:4e81d95e5bafc2d6e34e4bed780e53e4d5b9a2f928573428aa4d35fbec1eb0de", size = 3854095, upload-time = "2026-09-30T15:29:46.782Z" }, + { url = "https://files.pythonhosted.org/packages/1d/7a/f08d34ce09d60f89ebd391e2ebc6ba2b995e6dd7552f41820f8085f94e53/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:92e665960f25fcdc73725b9cec7a3824f279ba97a98653afe9ffac2e43668f67", size = 4716035, upload-time = "2026-09-30T15:29:48.681Z" }, + { url = "https://files.pythonhosted.org/packages/45/67/e18fb65592451a2acb76e9f2fbe14e0f47a8318b4c5430f1633851d03daa/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eef4c2f3423810b3070ab391f85436d2f8bbfcb286ac15cbc73190b3563b1f1a", size = 4726917, upload-time = "2026-09-30T15:29:50.608Z" }, + { url = "https://files.pythonhosted.org/packages/83/28/38fdce17e60f6b825e69fc3b7f75e70a6612759980704697e1de4cbfaf6e/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:7c6d0330c472d96f6a6afe24d80dfdf15176c33096f0a4397ae4c60f3dd3be48", size = 4715341, upload-time = "2026-09-30T15:29:52.522Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b1/d9121a717e0f893c64bd6ca7702614778d7df2a5c309128a002421788516/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:1ba34f04897fcdaa73f74145c25f3ec146fbd56593853e88adc2e811303c5f42", size = 4726322, upload-time = "2026-09-30T15:29:54.263Z" }, + { url = "https://files.pythonhosted.org/packages/36/8b/e6d153808bf353e152abd2fd4d8f09670d956ac78379ac46e60d7efbf04c/cryptography-50.0.2-pp311-pypy311_pp80-macosx_11_0_arm64.whl", hash = "sha256:3dc4fd8058cea1644971207d530e1a03a184a805ffc8ebdddf0599d78a331b81", size = 3873097, upload-time = "2026-09-30T15:29:56.097Z" }, + { url = "https://files.pythonhosted.org/packages/ca/1d/1271f287ff7170ddafc2aad36260c4eec20ccd2fea70f38455e9d56d427b/cryptography-50.0.2-pp311-pypy311_pp80-win_amd64.whl", hash = "sha256:7b75de3c8b3be1cdb1052747c929440c3eea46c1bc2cb8a6e3a48388e9b7b452", size = 3805376, upload-time = "2026-09-30T15:29:58.729Z" }, ] [[package]] From ac259b619eb46fe39494bd6db4d31cbf9c5fd534 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 01:03:31 -0400 Subject: [PATCH 43/65] chore(python): lock pyjwt 2.15 (PYSEC-2026-4140 and twelve more) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/fetch/uv.lock | 6 +++--- src/git/uv.lock | 6 +++--- src/time/uv.lock | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/fetch/uv.lock b/src/fetch/uv.lock index 64c1c7b94b..82067cab44 100644 --- a/src/fetch/uv.lock +++ b/src/fetch/uv.lock @@ -1002,14 +1002,14 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.13.0" +version = "2.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/ea/5194e52748b0da83d71e082d75496eaec6e58f419f5e184786ded517e6a9/pyjwt-2.15.1.tar.gz", hash = "sha256:4f259e80cdfb6b3fc18a7de51fd1ef9ec79652f25019bae68975ca2468a34df8", size = 121252, upload-time = "2026-09-28T18:40:42.598Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, + { url = "https://files.pythonhosted.org/packages/50/ca/44de4e75f8aadc457f0634be3b542815078ded46dca30efb960edeecad6e/pyjwt-2.15.1-py3-none-any.whl", hash = "sha256:42d59d631f7768a1028a64c7ff581a9bf7519804daf91fc5b6c56e30eec5e193", size = 33860, upload-time = "2026-09-28T18:40:41.429Z" }, ] [package.optional-dependencies] diff --git a/src/git/uv.lock b/src/git/uv.lock index 3921d8c8ed..39bec35e34 100644 --- a/src/git/uv.lock +++ b/src/git/uv.lock @@ -751,14 +751,14 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.13.0" +version = "2.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/ea/5194e52748b0da83d71e082d75496eaec6e58f419f5e184786ded517e6a9/pyjwt-2.15.1.tar.gz", hash = "sha256:4f259e80cdfb6b3fc18a7de51fd1ef9ec79652f25019bae68975ca2468a34df8", size = 121252, upload-time = "2026-09-28T18:40:42.598Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, + { url = "https://files.pythonhosted.org/packages/50/ca/44de4e75f8aadc457f0634be3b542815078ded46dca30efb960edeecad6e/pyjwt-2.15.1-py3-none-any.whl", hash = "sha256:42d59d631f7768a1028a64c7ff581a9bf7519804daf91fc5b6c56e30eec5e193", size = 33860, upload-time = "2026-09-28T18:40:41.429Z" }, ] [package.optional-dependencies] diff --git a/src/time/uv.lock b/src/time/uv.lock index 93cde3dd7e..fdb92e13d1 100644 --- a/src/time/uv.lock +++ b/src/time/uv.lock @@ -741,14 +741,14 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.13.0" +version = "2.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/ea/5194e52748b0da83d71e082d75496eaec6e58f419f5e184786ded517e6a9/pyjwt-2.15.1.tar.gz", hash = "sha256:4f259e80cdfb6b3fc18a7de51fd1ef9ec79652f25019bae68975ca2468a34df8", size = 121252, upload-time = "2026-09-28T18:40:42.598Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, + { url = "https://files.pythonhosted.org/packages/50/ca/44de4e75f8aadc457f0634be3b542815078ded46dca30efb960edeecad6e/pyjwt-2.15.1-py3-none-any.whl", hash = "sha256:42d59d631f7768a1028a64c7ff581a9bf7519804daf91fc5b6c56e30eec5e193", size = 33860, upload-time = "2026-09-28T18:40:41.429Z" }, ] [package.optional-dependencies] From ef6bffb7ac81482713aa2a092614322d04673bd9 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 01:03:31 -0400 Subject: [PATCH 44/65] chore(fetch): lock urllib3 2.8 (PYSEC-2026-4175, -4176, -4177) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/fetch/uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fetch/uv.lock b/src/fetch/uv.lock index 82067cab44..7aef782b58 100644 --- a/src/fetch/uv.lock +++ b/src/fetch/uv.lock @@ -1515,11 +1515,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.7.0" +version = "2.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, + { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, ] [[package]] From 478a08ef9be29e06e0bc966a41b3ab36c7b931ac Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 01:03:31 -0400 Subject: [PATCH 45/65] chore(fetch): lock soupsieve 2.10 (PYSEC-2026-4170, -4171) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/fetch/uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fetch/uv.lock b/src/fetch/uv.lock index 7aef782b58..2b9d970293 100644 --- a/src/fetch/uv.lock +++ b/src/fetch/uv.lock @@ -1396,11 +1396,11 @@ wheels = [ [[package]] name = "soupsieve" -version = "2.8.4" +version = "2.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/c3/1b817965ac12dc002d7c9cd7dfffdd7d4fbf9b45763ef2ffe7b86ee94670/soupsieve-2.10.tar.gz", hash = "sha256:49e9380d7d2905463583bafe285e818c7366a9ed7b3aee221c1ac79c905d8bc0", size = 124337, upload-time = "2026-09-24T02:36:35.048Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" }, + { url = "https://files.pythonhosted.org/packages/66/87/5ed59e1d0290564e2027ed066c52b059892c4637741337fc0967183c8d4d/soupsieve-2.10-py3-none-any.whl", hash = "sha256:8596eb8967d744174820280fa62b4542a2e955bfaccca73ed8a13c6eb8e9b502", size = 38019, upload-time = "2026-09-24T02:36:33.709Z" }, ] [[package]] From 0c505a6114c58e0243761b842b2ce2893b6d21af Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 01:05:02 -0400 Subject: [PATCH 46/65] chore: fold the pending changesets into a 1.0.0 CHANGELOG entry per TypeScript server Generated by the repo's version-packages script (changeset version with @changesets/changelog-github), then each new 2.0.0 heading retitled 1.0.0 and the package.json and lockfile changes discarded: all four servers already read 1.0.0, which has never been published. Every pending changeset is consumed, so the next Version Packages PR bumps only for modern-era work. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .changeset/everything-blob-mime-type.md | 5 ---- .../everything-conditional-tools-4792.md | 5 ---- .changeset/everything-no-tests-in-dist.md | 5 ---- .changeset/everything-sdk-v2.md | 5 ---- ...everything-session-resources-per-server.md | 5 ---- .changeset/everything-sse-session-errors.md | 5 ---- .../everything-streamable-http-sessions.md | 5 ---- .changeset/everything-testable-transports.md | 5 ---- .changeset/filesystem-create-server.md | 5 ---- .../filesystem-edit-line-endings-indent.md | 5 ---- .changeset/filesystem-in-place-write.md | 5 ---- .changeset/filesystem-initial-roots.md | 5 ---- .changeset/filesystem-schema-dialect.md | 5 ---- .changeset/filesystem-sdk-v2.md | 5 ---- .changeset/filesystem-unc-share-root.md | 5 ---- .changeset/filesystem-unicode-paths.md | 5 ---- .changeset/memory-create-entities-skipped.md | 5 ---- .changeset/memory-create-server-factory.md | 5 ---- .changeset/memory-cross-process-lock.md | 5 ---- .changeset/memory-file-path-base-dir.md | 5 ---- .changeset/memory-keep-unreadable-lines.md | 5 ---- .changeset/memory-no-tests-in-dist.md | 5 ---- .changeset/memory-preserve-file-mode.md | 5 ---- .changeset/memory-sdk-v2.md | 5 ---- .changeset/sequentialthinking-annotations.md | 5 ---- .../sequentialthinking-create-server.md | 5 ---- .changeset/sequentialthinking-log-header.md | 5 ---- .../sequentialthinking-no-tests-in-dist.md | 5 ---- .../sequentialthinking-prototype-branchid.md | 5 ---- .changeset/sequentialthinking-sdk-v2.md | 5 ---- .changeset/sse-port-in-use.md | 5 ---- src/everything/CHANGELOG.md | 27 +++++++++++++++++++ src/filesystem/CHANGELOG.md | 25 +++++++++++++++++ src/memory/CHANGELOG.md | 25 +++++++++++++++++ src/sequentialthinking/CHANGELOG.md | 21 +++++++++++++++ 35 files changed, 98 insertions(+), 155 deletions(-) delete mode 100644 .changeset/everything-blob-mime-type.md delete mode 100644 .changeset/everything-conditional-tools-4792.md delete mode 100644 .changeset/everything-no-tests-in-dist.md delete mode 100644 .changeset/everything-sdk-v2.md delete mode 100644 .changeset/everything-session-resources-per-server.md delete mode 100644 .changeset/everything-sse-session-errors.md delete mode 100644 .changeset/everything-streamable-http-sessions.md delete mode 100644 .changeset/everything-testable-transports.md delete mode 100644 .changeset/filesystem-create-server.md delete mode 100644 .changeset/filesystem-edit-line-endings-indent.md delete mode 100644 .changeset/filesystem-in-place-write.md delete mode 100644 .changeset/filesystem-initial-roots.md delete mode 100644 .changeset/filesystem-schema-dialect.md delete mode 100644 .changeset/filesystem-sdk-v2.md delete mode 100644 .changeset/filesystem-unc-share-root.md delete mode 100644 .changeset/filesystem-unicode-paths.md delete mode 100644 .changeset/memory-create-entities-skipped.md delete mode 100644 .changeset/memory-create-server-factory.md delete mode 100644 .changeset/memory-cross-process-lock.md delete mode 100644 .changeset/memory-file-path-base-dir.md delete mode 100644 .changeset/memory-keep-unreadable-lines.md delete mode 100644 .changeset/memory-no-tests-in-dist.md delete mode 100644 .changeset/memory-preserve-file-mode.md delete mode 100644 .changeset/memory-sdk-v2.md delete mode 100644 .changeset/sequentialthinking-annotations.md delete mode 100644 .changeset/sequentialthinking-create-server.md delete mode 100644 .changeset/sequentialthinking-log-header.md delete mode 100644 .changeset/sequentialthinking-no-tests-in-dist.md delete mode 100644 .changeset/sequentialthinking-prototype-branchid.md delete mode 100644 .changeset/sequentialthinking-sdk-v2.md delete mode 100644 .changeset/sse-port-in-use.md create mode 100644 src/everything/CHANGELOG.md create mode 100644 src/filesystem/CHANGELOG.md create mode 100644 src/memory/CHANGELOG.md create mode 100644 src/sequentialthinking/CHANGELOG.md diff --git a/.changeset/everything-blob-mime-type.md b/.changeset/everything-blob-mime-type.md deleted file mode 100644 index 8129a3a1e7..0000000000 --- a/.changeset/everything-blob-mime-type.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-everything": patch ---- - -Label dynamic blob resources `application/octet-stream`, matching their resource template, instead of `text/plain`. This applies to `resources/read`, the `get-resource-reference` and `get-resource-links` tools, and the `resource-prompt` prompt; blob resource links are now described as "binary blob resource". diff --git a/.changeset/everything-conditional-tools-4792.md b/.changeset/everything-conditional-tools-4792.md deleted file mode 100644 index 0b07bc7238..0000000000 --- a/.changeset/everything-conditional-tools-4792.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-everything": patch ---- - -Server instructions no longer tell every client to use tools that are registered only for clients declaring a capability; they list those tools and the capability each needs (#4792, ported from #4835). `trigger-elicitation-request` is now registered only for clients that support form-mode elicitation, so a client that declared only URL-mode elicitation no longer sees a tool it cannot answer (#4985). diff --git a/.changeset/everything-no-tests-in-dist.md b/.changeset/everything-no-tests-in-dist.md deleted file mode 100644 index 3be8be9adf..0000000000 --- a/.changeset/everything-no-tests-in-dist.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-everything": patch ---- - -Stop shipping the compiled test suite and Vitest config in the published package: the build now excludes `__tests__/`, `*.test.ts`, `*.spec.ts` and `vitest.config.ts`, as the other TypeScript servers' builds do. diff --git a/.changeset/everything-sdk-v2.md b/.changeset/everything-sdk-v2.md deleted file mode 100644 index 07fe4f38ae..0000000000 --- a/.changeset/everything-sdk-v2.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-everything": major ---- - -Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`, `@modelcontextprotocol/node`, and `@modelcontextprotocol/server-legacy` for the deprecated HTTP+SSE transport) from `@modelcontextprotocol/sdk` 1.x (#4856). The task tools `simulate-research-query`, `trigger-sampling-request-async` and `trigger-elicitation-request-async` are removed, and the server no longer advertises the `tasks` capability, because SDK v2 dropped the experimental tasks layer they were built on; they return on the tasks extension in #4852. Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. The optional `state` argument of `args-prompt` now carries its description. diff --git a/.changeset/everything-session-resources-per-server.md b/.changeset/everything-session-resources-per-server.md deleted file mode 100644 index caea9ac495..0000000000 --- a/.changeset/everything-session-resources-per-server.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-everything": patch ---- - -Session resources are now tracked per server, so two sessions that create a session resource with the same name (for example via `gzip-file-as-resource`) no longer evict each other's resource (#4808). diff --git a/.changeset/everything-sse-session-errors.md b/.changeset/everything-sse-session-errors.md deleted file mode 100644 index 974a314da9..0000000000 --- a/.changeset/everything-sse-session-errors.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-everything": patch ---- - -The HTTP+SSE transport now answers requests for sessions it cannot serve instead of leaving them hanging or failing with a 500: `POST /message` for an unknown session returns `404` (and `400` with no `sessionId`), and `GET /sse?sessionId=…` returns `409` for a session that already has its stream and `404` for an unknown one. Each carries a JSON-RPC error body. diff --git a/.changeset/everything-streamable-http-sessions.md b/.changeset/everything-streamable-http-sessions.md deleted file mode 100644 index 721e1f7f0e..0000000000 --- a/.changeset/everything-streamable-http-sessions.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-everything": patch ---- - -Streamable HTTP: answer an unknown or ended session ID with `404 Not Found` (carrying the request's `id` on a POST) instead of `400`; replay only the resumed stream's events after a `Last-Event-ID`, and refuse an unknown one; close every open session on shutdown. diff --git a/.changeset/everything-testable-transports.md b/.changeset/everything-testable-transports.md deleted file mode 100644 index 5f8b911475..0000000000 --- a/.changeset/everything-testable-transports.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-everything": patch ---- - -Internal refactor for in-process testing, with no change in behavior: the launcher only starts a transport when run as the binary, and the stdio, SSE and Streamable HTTP transport modules export their start-up (`startStdioServer`, `startSseServer`, `startStreamableHttpServer`, and `createApp()` for the HTTP transports) instead of starting a server when imported. diff --git a/.changeset/filesystem-create-server.md b/.changeset/filesystem-create-server.md deleted file mode 100644 index 7c26e314d5..0000000000 --- a/.changeset/filesystem-create-server.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-filesystem": patch ---- - -Internal: the server is now built by a `createServer()` factory in `server.ts`, and `index.ts` starts it over stdio only when run as the bin, with each server instance holding its own allowed directories. No change to the tools, their results, or how the allowed directories are resolved from arguments and Roots. diff --git a/.changeset/filesystem-edit-line-endings-indent.md b/.changeset/filesystem-edit-line-endings-indent.md deleted file mode 100644 index 3e362fd793..0000000000 --- a/.changeset/filesystem-edit-line-endings-indent.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-filesystem": patch ---- - -`edit_file` keeps a file's line endings: a CRLF file is written back with CRLF instead of being converted to LF. When `oldText` has no exact match and the whitespace-tolerant matcher is used, each replacement line now takes the indentation of the file line it replaces, shifted by its indentation relative to the matching `oldText` line, instead of only the first line being reindented. diff --git a/.changeset/filesystem-in-place-write.md b/.changeset/filesystem-in-place-write.md deleted file mode 100644 index 45ec2c4648..0000000000 --- a/.changeset/filesystem-in-place-write.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-filesystem": patch ---- - -`write_file` and `edit_file` now overwrite an existing file in place instead of renaming a temp file over it, so the file keeps its inode, creation time (birthtime), hard links and permission bits (#4512), and an overwrite no longer fails with `EPERM` on Windows when another process holds the file open (#3199). A symlink swapped in after the path was validated is still refused rather than written through, now by an `O_NOFOLLOW` open (on POSIX) and a check that the opened file is the one validated (on every platform). The overwrite is no longer crash-atomic. A read-only file is now refused (`EACCES`, or `EPERM` on Windows) instead of being replaced, since writing in place opens the file itself for writing. diff --git a/.changeset/filesystem-initial-roots.md b/.changeset/filesystem-initial-roots.md deleted file mode 100644 index b887385c51..0000000000 --- a/.changeset/filesystem-initial-roots.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-filesystem": patch ---- - -Tool calls now wait for the client's initial roots before checking paths, so a call sent right after connecting is no longer refused with "Access denied" (#3204). A server started with no directories, connected to a client without Roots support, now fails visibly: it logs the reason, closes the connection and exits with status 1, instead of staying up and refusing every call (#4992). diff --git a/.changeset/filesystem-schema-dialect.md b/.changeset/filesystem-schema-dialect.md deleted file mode 100644 index 0579ae918b..0000000000 --- a/.changeset/filesystem-schema-dialect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-filesystem": patch ---- - -Every tool's input and output schema in `tools/list` now declares `"$schema": "https://json-schema.org/draft/2020-12/schema"` instead of draft-07, so clients that validate tool schemas strictly against JSON Schema 2020-12 no longer reject every tool. The schemas are otherwise unchanged. diff --git a/.changeset/filesystem-sdk-v2.md b/.changeset/filesystem-sdk-v2.md deleted file mode 100644 index 523a805d57..0000000000 --- a/.changeset/filesystem-sdk-v2.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-filesystem": major ---- - -Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x (#4856). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); and tools no longer advertise `execution.taskSupport`. diff --git a/.changeset/filesystem-unc-share-root.md b/.changeset/filesystem-unc-share-root.md deleted file mode 100644 index df29e88f31..0000000000 --- a/.changeset/filesystem-unc-share-root.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-filesystem": patch ---- - -On Windows, a UNC share root given as an allowed directory (`\\server\share` or `\\server\share\`) now admits the files and subdirectories under it. Before, only the share root itself was accessible and every path below it was refused. Sibling shares such as `\\server\share-evil` are still refused. diff --git a/.changeset/filesystem-unicode-paths.md b/.changeset/filesystem-unicode-paths.md deleted file mode 100644 index 1f24c07e6d..0000000000 --- a/.changeset/filesystem-unicode-paths.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-filesystem": patch ---- - -Paths that differ from the names on disk only in Unicode form now resolve (#1970). An NFC spelling of an allowed directory whose name is stored NFD (macOS "Capture d’écran", Japanese dakuten) is no longer refused as outside the allowed directories, and a request that spells a U+202F or U+00A0 in a name as a plain space (the macOS screenshot "Screenshot … at 2.40.40 PM.png") finds the file instead of failing with ENOENT. The exact spelling still wins when it exists, and a name matching two entries is refused as ambiguous. diff --git a/.changeset/memory-create-entities-skipped.md b/.changeset/memory-create-entities-skipped.md deleted file mode 100644 index 5ed174763d..0000000000 --- a/.changeset/memory-create-entities-skipped.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-memory": patch ---- - -`create_entities` now reports the entities it skipped because their name already exists (or repeats earlier in the same call): the structured result lists them in a new optional `skipped` array, a second text item names them and points to `add_observations`, and the tool description says so. Skipped entities are still not created and their observations are still not added (#4887). diff --git a/.changeset/memory-create-server-factory.md b/.changeset/memory-create-server-factory.md deleted file mode 100644 index 710e9243ca..0000000000 --- a/.changeset/memory-create-server-factory.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-memory": patch ---- - -Build the server with an exported `createServer()` factory and start stdio only when the package is run as a program, so importing the module no longer starts a server. Behavior over stdio is unchanged. diff --git a/.changeset/memory-cross-process-lock.md b/.changeset/memory-cross-process-lock.md deleted file mode 100644 index 32b61ec2d8..0000000000 --- a/.changeset/memory-cross-process-lock.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-memory": patch ---- - -Two server processes sharing one memory file no longer silently discard each other's writes: every write tool now holds an exclusive lock file (`.lock`) while it reads, changes and saves the graph, and a lock left by a crashed server is recovered automatically (#4797). diff --git a/.changeset/memory-file-path-base-dir.md b/.changeset/memory-file-path-base-dir.md deleted file mode 100644 index ec2f84b554..0000000000 --- a/.changeset/memory-file-path-base-dir.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-memory": patch ---- - -Internal: `ensureMemoryFilePath` accepts the directory its default files live in, so the server's tests no longer write into the package directory. No change to how the server chooses or migrates its memory file. diff --git a/.changeset/memory-keep-unreadable-lines.md b/.changeset/memory-keep-unreadable-lines.md deleted file mode 100644 index 71a30793ff..0000000000 --- a/.changeset/memory-keep-unreadable-lines.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-memory": patch ---- - -Lines in the memory file that the server cannot read (malformed JSON, an entity or relation that fails validation, an unknown record type) are no longer deleted by the next write. They are still left out of the graph, and are now written back unchanged after the graph's own lines. diff --git a/.changeset/memory-no-tests-in-dist.md b/.changeset/memory-no-tests-in-dist.md deleted file mode 100644 index 8d7588cd11..0000000000 --- a/.changeset/memory-no-tests-in-dist.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-memory": patch ---- - -Stop shipping the compiled test helper `dist/__tests__/helpers.js` in the published package: the build now excludes everything under `__tests__/`, not just `*.test.ts`. diff --git a/.changeset/memory-preserve-file-mode.md b/.changeset/memory-preserve-file-mode.md deleted file mode 100644 index a0180a0f66..0000000000 --- a/.changeset/memory-preserve-file-mode.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-memory": patch ---- - -Saving the knowledge graph keeps the memory file's permission bits (an operator's `0600` no longer comes back `0644`), and a write to a read-only memory file now fails with `EACCES` instead of silently replacing it (#4827). diff --git a/.changeset/memory-sdk-v2.md b/.changeset/memory-sdk-v2.md deleted file mode 100644 index e1ab5b57df..0000000000 --- a/.changeset/memory-sdk-v2.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-memory": major ---- - -Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x (#4856). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. diff --git a/.changeset/sequentialthinking-annotations.md b/.changeset/sequentialthinking-annotations.md deleted file mode 100644 index 6c4974d857..0000000000 --- a/.changeset/sequentialthinking-annotations.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-sequential-thinking": patch ---- - -Advertise the `sequentialthinking` tool as `readOnlyHint: false` and `idempotentHint: false`. Every call appends to the server's thought history, and a call with both `branchFromThought` and `branchId` also appends to that branch, so the tool was never read-only or idempotent. `destructiveHint` and `openWorldHint` stay `false`. (#4721) diff --git a/.changeset/sequentialthinking-create-server.md b/.changeset/sequentialthinking-create-server.md deleted file mode 100644 index 91f8072d8a..0000000000 --- a/.changeset/sequentialthinking-create-server.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-sequential-thinking": patch ---- - -Build the server with an exported `createServer()` factory and attach stdio only when `index.js` runs as the binary, so the server can be tested in-process. No change to the tool, its schemas or its results. diff --git a/.changeset/sequentialthinking-log-header.md b/.changeset/sequentialthinking-log-header.md deleted file mode 100644 index aca9fdcfdf..0000000000 --- a/.changeset/sequentialthinking-log-header.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-sequential-thinking": patch ---- - -The thought log on stderr no longer prints "undefined" in its headers: a revision with no `revisesThought` reads "Revision N/M", and a branch with no `branchId` reads "(from thought N)" with no ID. The box border is sized from the header's visible text, so colour escape codes no longer make it wider than its text. diff --git a/.changeset/sequentialthinking-no-tests-in-dist.md b/.changeset/sequentialthinking-no-tests-in-dist.md deleted file mode 100644 index 7b760969f9..0000000000 --- a/.changeset/sequentialthinking-no-tests-in-dist.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-sequential-thinking": patch ---- - -Stop shipping the compiled test helper `dist/__tests__/helpers.js` in the published package: the build now excludes everything under `__tests__/`, not just `*.test.ts`. diff --git a/.changeset/sequentialthinking-prototype-branchid.md b/.changeset/sequentialthinking-prototype-branchid.md deleted file mode 100644 index dbc380da45..0000000000 --- a/.changeset/sequentialthinking-prototype-branchid.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-sequential-thinking": patch ---- - -A `branchId` that names an `Object.prototype` key, such as `"constructor"` or `"__proto__"`, now creates and lists its branch like any other id instead of failing the call. A call that fails no longer adds its thought to the history first, so `thoughtHistoryLength` counts only thoughts that were accepted. diff --git a/.changeset/sequentialthinking-sdk-v2.md b/.changeset/sequentialthinking-sdk-v2.md deleted file mode 100644 index 9e76a69e93..0000000000 --- a/.changeset/sequentialthinking-sdk-v2.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-sequential-thinking": major ---- - -Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x (#4856). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. diff --git a/.changeset/sse-port-in-use.md b/.changeset/sse-port-in-use.md deleted file mode 100644 index 59c5dbd55e..0000000000 --- a/.changeset/sse-port-in-use.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-everything": patch ---- - -The HTTP+SSE transport no longer prints "Server is running" and stays up when its port is already in use: it reports the port and exits non-zero, as Streamable HTTP does. Streamable HTTP no longer prints its listening line before that error. diff --git a/src/everything/CHANGELOG.md b/src/everything/CHANGELOG.md new file mode 100644 index 0000000000..5cf0063995 --- /dev/null +++ b/src/everything/CHANGELOG.md @@ -0,0 +1,27 @@ +# @modelcontextprotocol/server-everything + +## 1.0.0 + +The first semantic version, resuming semver after the date-stamped releases (last `2026.8.31`). It describes everything since `2026.8.31`, and is the last release of the legacy-era server. + +### Major Changes + +- [#5063](https://github.com/modelcontextprotocol/servers/pull/5063) [`b5af226`](https://github.com/modelcontextprotocol/servers/commit/b5af22668592700c02236d737f9836c42654edb6) Thanks [@cliffhall](https://github.com/cliffhall)! - Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`, `@modelcontextprotocol/node`, and `@modelcontextprotocol/server-legacy` for the deprecated HTTP+SSE transport) from `@modelcontextprotocol/sdk` 1.x ([#4856](https://github.com/modelcontextprotocol/servers/issues/4856)). The task tools `simulate-research-query`, `trigger-sampling-request-async` and `trigger-elicitation-request-async` are removed, and the server no longer advertises the `tasks` capability, because SDK v2 dropped the experimental tasks layer they were built on; they return on the tasks extension in [#4852](https://github.com/modelcontextprotocol/servers/issues/4852). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. The optional `state` argument of `args-prompt` now carries its description. + +### Patch Changes + +- [#5005](https://github.com/modelcontextprotocol/servers/pull/5005) [`63dc92f`](https://github.com/modelcontextprotocol/servers/commit/63dc92f0e95992faf9d3e339cb8a51b1090d138b) Thanks [@cliffhall](https://github.com/cliffhall)! - Label dynamic blob resources `application/octet-stream`, matching their resource template, instead of `text/plain`. This applies to `resources/read`, the `get-resource-reference` and `get-resource-links` tools, and the `resource-prompt` prompt; blob resource links are now described as "binary blob resource". + +- [#5034](https://github.com/modelcontextprotocol/servers/pull/5034) [`6bed4c5`](https://github.com/modelcontextprotocol/servers/commit/6bed4c5f7c219e77fe90b9e4b14cdb9f3f7e9503) Thanks [@cliffhall](https://github.com/cliffhall)! - Server instructions no longer tell every client to use tools that are registered only for clients declaring a capability; they list those tools and the capability each needs ([#4792](https://github.com/modelcontextprotocol/servers/issues/4792), ported from [#4835](https://github.com/modelcontextprotocol/servers/issues/4835)). `trigger-elicitation-request` is now registered only for clients that support form-mode elicitation, so a client that declared only URL-mode elicitation no longer sees a tool it cannot answer ([#4985](https://github.com/modelcontextprotocol/servers/issues/4985)). + +- [#5053](https://github.com/modelcontextprotocol/servers/pull/5053) [`c63255a`](https://github.com/modelcontextprotocol/servers/commit/c63255ae9616b110d6089fa343fcfdf3f5def534) Thanks [@cliffhall](https://github.com/cliffhall)! - Stop shipping the compiled test suite and Vitest config in the published package: the build now excludes `__tests__/`, `*.test.ts`, `*.spec.ts` and `vitest.config.ts`, as the other TypeScript servers' builds do. + +- [#5032](https://github.com/modelcontextprotocol/servers/pull/5032) [`d4fb2f4`](https://github.com/modelcontextprotocol/servers/commit/d4fb2f4f11efc084d97958dbecefa12c03020bd9) Thanks [@cliffhall](https://github.com/cliffhall)! - Session resources are now tracked per server, so two sessions that create a session resource with the same name (for example via `gzip-file-as-resource`) no longer evict each other's resource ([#4808](https://github.com/modelcontextprotocol/servers/issues/4808)). + +- [#5020](https://github.com/modelcontextprotocol/servers/pull/5020) [`f7af617`](https://github.com/modelcontextprotocol/servers/commit/f7af617ead2d4c7faf10338159a280d6387a1475) Thanks [@cliffhall](https://github.com/cliffhall)! - The HTTP+SSE transport now answers requests for sessions it cannot serve instead of leaving them hanging or failing with a 500: `POST /message` for an unknown session returns `404` (and `400` with no `sessionId`), and `GET /sse?sessionId=…` returns `409` for a session that already has its stream and `404` for an unknown one. Each carries a JSON-RPC error body. + +- [#5006](https://github.com/modelcontextprotocol/servers/pull/5006) [`dbd1b2e`](https://github.com/modelcontextprotocol/servers/commit/dbd1b2e699714074c6355ddf60e1fb832f139d7f) Thanks [@cliffhall](https://github.com/cliffhall)! - Streamable HTTP: answer an unknown or ended session ID with `404 Not Found` (carrying the request's `id` on a POST) instead of `400`; replay only the resumed stream's events after a `Last-Event-ID`, and refuse an unknown one; close every open session on shutdown. + +- [#4970](https://github.com/modelcontextprotocol/servers/pull/4970) [`c449b5e`](https://github.com/modelcontextprotocol/servers/commit/c449b5e71b7c9cc926a39799a2dcf0ec401d3a20) Thanks [@cliffhall](https://github.com/cliffhall)! - Internal refactor for in-process testing, with no change in behavior: the launcher only starts a transport when run as the binary, and the stdio, SSE and Streamable HTTP transport modules export their start-up (`startStdioServer`, `startSseServer`, `startStreamableHttpServer`, and `createApp()` for the HTTP transports) instead of starting a server when imported. + +- [#4936](https://github.com/modelcontextprotocol/servers/pull/4936) [`feb251d`](https://github.com/modelcontextprotocol/servers/commit/feb251d2fd0057570125ba0ba25e98c84f8c5b5c) Thanks [@cliffhall](https://github.com/cliffhall)! - The HTTP+SSE transport no longer prints "Server is running" and stays up when its port is already in use: it reports the port and exits non-zero, as Streamable HTTP does. Streamable HTTP no longer prints its listening line before that error. diff --git a/src/filesystem/CHANGELOG.md b/src/filesystem/CHANGELOG.md new file mode 100644 index 0000000000..bd71316b1a --- /dev/null +++ b/src/filesystem/CHANGELOG.md @@ -0,0 +1,25 @@ +# @modelcontextprotocol/server-filesystem + +## 1.0.0 + +The first semantic version, resuming semver after the date-stamped releases (last `2026.8.31`). It describes everything since `2026.8.31`, and is the last release of the legacy-era server. + +### Major Changes + +- [#5063](https://github.com/modelcontextprotocol/servers/pull/5063) [`b5af226`](https://github.com/modelcontextprotocol/servers/commit/b5af22668592700c02236d737f9836c42654edb6) Thanks [@cliffhall](https://github.com/cliffhall)! - Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x ([#4856](https://github.com/modelcontextprotocol/servers/issues/4856)). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); and tools no longer advertise `execution.taskSupport`. + +### Patch Changes + +- [#4970](https://github.com/modelcontextprotocol/servers/pull/4970) [`3a63f52`](https://github.com/modelcontextprotocol/servers/commit/3a63f52e751882e20b24e8f0c840d402348bbd72) Thanks [@cliffhall](https://github.com/cliffhall)! - Internal: the server is now built by a `createServer()` factory in `server.ts`, and `index.ts` starts it over stdio only when run as the bin, with each server instance holding its own allowed directories. No change to the tools, their results, or how the allowed directories are resolved from arguments and Roots. + +- [#5011](https://github.com/modelcontextprotocol/servers/pull/5011) [`d8ebdbe`](https://github.com/modelcontextprotocol/servers/commit/d8ebdbef770768c05d9fddbb663bc6f380a08867) Thanks [@cliffhall](https://github.com/cliffhall)! - `edit_file` keeps a file's line endings: a CRLF file is written back with CRLF instead of being converted to LF. When `oldText` has no exact match and the whitespace-tolerant matcher is used, each replacement line now takes the indentation of the file line it replaces, shifted by its indentation relative to the matching `oldText` line, instead of only the first line being reindented. + +- [#5022](https://github.com/modelcontextprotocol/servers/pull/5022) [`7dc802f`](https://github.com/modelcontextprotocol/servers/commit/7dc802f914a727e840b65712b0a9fbaf7fe1c2b5) Thanks [@cliffhall](https://github.com/cliffhall)! - `write_file` and `edit_file` now overwrite an existing file in place instead of renaming a temp file over it, so the file keeps its inode, creation time (birthtime), hard links and permission bits ([#4512](https://github.com/modelcontextprotocol/servers/issues/4512)), and an overwrite no longer fails with `EPERM` on Windows when another process holds the file open ([#3199](https://github.com/modelcontextprotocol/servers/issues/3199)). A symlink swapped in after the path was validated is still refused rather than written through, now by an `O_NOFOLLOW` open (on POSIX) and a check that the opened file is the one validated (on every platform). The overwrite is no longer crash-atomic. A read-only file is now refused (`EACCES`, or `EPERM` on Windows) instead of being replaced, since writing in place opens the file itself for writing. + +- [#5025](https://github.com/modelcontextprotocol/servers/pull/5025) [`2309e08`](https://github.com/modelcontextprotocol/servers/commit/2309e089b12385650e52784e26668aabdbc9b74b) Thanks [@cliffhall](https://github.com/cliffhall)! - Tool calls now wait for the client's initial roots before checking paths, so a call sent right after connecting is no longer refused with "Access denied" ([#3204](https://github.com/modelcontextprotocol/servers/issues/3204)). A server started with no directories, connected to a client without Roots support, now fails visibly: it logs the reason, closes the connection and exits with status 1, instead of staying up and refusing every call ([#4992](https://github.com/modelcontextprotocol/servers/issues/4992)). + +- [#5030](https://github.com/modelcontextprotocol/servers/pull/5030) [`5c04d74`](https://github.com/modelcontextprotocol/servers/commit/5c04d74ff6430ae82763aae76b3b1e585f5f13fd) Thanks [@cliffhall](https://github.com/cliffhall)! - Every tool's input and output schema in `tools/list` now declares `"$schema": "https://json-schema.org/draft/2020-12/schema"` instead of draft-07, so clients that validate tool schemas strictly against JSON Schema 2020-12 no longer reject every tool. The schemas are otherwise unchanged. + +- [#5010](https://github.com/modelcontextprotocol/servers/pull/5010) [`fd4dff7`](https://github.com/modelcontextprotocol/servers/commit/fd4dff7da877068734bee9419fa935213d66e359) Thanks [@cliffhall](https://github.com/cliffhall)! - On Windows, a UNC share root given as an allowed directory (`\\server\share` or `\\server\share\`) now admits the files and subdirectories under it. Before, only the share root itself was accessible and every path below it was refused. Sibling shares such as `\\server\share-evil` are still refused. + +- [#5009](https://github.com/modelcontextprotocol/servers/pull/5009) [`f621c75`](https://github.com/modelcontextprotocol/servers/commit/f621c75a92347738f28f26c9461286764beba3c8) Thanks [@cliffhall](https://github.com/cliffhall)! - Paths that differ from the names on disk only in Unicode form now resolve ([#1970](https://github.com/modelcontextprotocol/servers/issues/1970)). An NFC spelling of an allowed directory whose name is stored NFD (macOS "Capture d’écran", Japanese dakuten) is no longer refused as outside the allowed directories, and a request that spells a U+202F or U+00A0 in a name as a plain space (the macOS screenshot "Screenshot … at 2.40.40 PM.png") finds the file instead of failing with ENOENT. The exact spelling still wins when it exists, and a name matching two entries is refused as ambiguous. diff --git a/src/memory/CHANGELOG.md b/src/memory/CHANGELOG.md new file mode 100644 index 0000000000..30d1530203 --- /dev/null +++ b/src/memory/CHANGELOG.md @@ -0,0 +1,25 @@ +# @modelcontextprotocol/server-memory + +## 1.0.0 + +The first semantic version, resuming semver after the date-stamped releases (last `2026.8.31`). It describes everything since `2026.8.31`, and is the last release of the legacy-era server. + +### Major Changes + +- [#5063](https://github.com/modelcontextprotocol/servers/pull/5063) [`b5af226`](https://github.com/modelcontextprotocol/servers/commit/b5af22668592700c02236d737f9836c42654edb6) Thanks [@cliffhall](https://github.com/cliffhall)! - Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x ([#4856](https://github.com/modelcontextprotocol/servers/issues/4856)). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. + +### Patch Changes + +- [#5008](https://github.com/modelcontextprotocol/servers/pull/5008) [`bb8aa6f`](https://github.com/modelcontextprotocol/servers/commit/bb8aa6fd73b2a2fd68bc7084292296d2b0084780) Thanks [@cliffhall](https://github.com/cliffhall)! - `create_entities` now reports the entities it skipped because their name already exists (or repeats earlier in the same call): the structured result lists them in a new optional `skipped` array, a second text item names them and points to `add_observations`, and the tool description says so. Skipped entities are still not created and their observations are still not added ([#4887](https://github.com/modelcontextprotocol/servers/issues/4887)). + +- [#4970](https://github.com/modelcontextprotocol/servers/pull/4970) [`a8127ba`](https://github.com/modelcontextprotocol/servers/commit/a8127ba956c12c7f6597b43765f1f7a68aee0d7e) Thanks [@cliffhall](https://github.com/cliffhall)! - Build the server with an exported `createServer()` factory and start stdio only when the package is run as a program, so importing the module no longer starts a server. Behavior over stdio is unchanged. + +- [#5037](https://github.com/modelcontextprotocol/servers/pull/5037) [`7be08e9`](https://github.com/modelcontextprotocol/servers/commit/7be08e97f399721c215de52bfb74a5030c20f78f) Thanks [@cliffhall](https://github.com/cliffhall)! - Two server processes sharing one memory file no longer silently discard each other's writes: every write tool now holds an exclusive lock file (`.lock`) while it reads, changes and saves the graph, and a lock left by a crashed server is recovered automatically ([#4797](https://github.com/modelcontextprotocol/servers/issues/4797)). + +- [#4937](https://github.com/modelcontextprotocol/servers/pull/4937) [`478db6a`](https://github.com/modelcontextprotocol/servers/commit/478db6a5d5bc0bec3196e2c8c9461ec1597ded34) Thanks [@cliffhall](https://github.com/cliffhall)! - Internal: `ensureMemoryFilePath` accepts the directory its default files live in, so the server's tests no longer write into the package directory. No change to how the server chooses or migrates its memory file. + +- [#5035](https://github.com/modelcontextprotocol/servers/pull/5035) [`2b1305f`](https://github.com/modelcontextprotocol/servers/commit/2b1305fc0db794cdf1bd57ca83d13fb8a195e869) Thanks [@cliffhall](https://github.com/cliffhall)! - Lines in the memory file that the server cannot read (malformed JSON, an entity or relation that fails validation, an unknown record type) are no longer deleted by the next write. They are still left out of the graph, and are now written back unchanged after the graph's own lines. + +- [#5053](https://github.com/modelcontextprotocol/servers/pull/5053) [`c63255a`](https://github.com/modelcontextprotocol/servers/commit/c63255ae9616b110d6089fa343fcfdf3f5def534) Thanks [@cliffhall](https://github.com/cliffhall)! - Stop shipping the compiled test helper `dist/__tests__/helpers.js` in the published package: the build now excludes everything under `__tests__/`, not just `*.test.ts`. + +- [#5013](https://github.com/modelcontextprotocol/servers/pull/5013) [`819ae90`](https://github.com/modelcontextprotocol/servers/commit/819ae904cc08ab0b208ccb91addc3169b3ece85b) Thanks [@cliffhall](https://github.com/cliffhall)! - Saving the knowledge graph keeps the memory file's permission bits (an operator's `0600` no longer comes back `0644`), and a write to a read-only memory file now fails with `EACCES` instead of silently replacing it ([#4827](https://github.com/modelcontextprotocol/servers/issues/4827)). diff --git a/src/sequentialthinking/CHANGELOG.md b/src/sequentialthinking/CHANGELOG.md new file mode 100644 index 0000000000..352bd55a4b --- /dev/null +++ b/src/sequentialthinking/CHANGELOG.md @@ -0,0 +1,21 @@ +# @modelcontextprotocol/server-sequential-thinking + +## 1.0.0 + +The first semantic version, resuming semver after the date-stamped releases (last `2026.8.31`). It describes everything since `2026.8.31`, and is the last release of the legacy-era server. + +### Major Changes + +- [#5063](https://github.com/modelcontextprotocol/servers/pull/5063) [`b5af226`](https://github.com/modelcontextprotocol/servers/commit/b5af22668592700c02236d737f9836c42654edb6) Thanks [@cliffhall](https://github.com/cliffhall)! - Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x ([#4856](https://github.com/modelcontextprotocol/servers/issues/4856)). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. + +### Patch Changes + +- [#5015](https://github.com/modelcontextprotocol/servers/pull/5015) [`8a4139c`](https://github.com/modelcontextprotocol/servers/commit/8a4139c3d71b66e95d2f7cd2bc485d4781ecf1d7) Thanks [@cliffhall](https://github.com/cliffhall)! - Advertise the `sequentialthinking` tool as `readOnlyHint: false` and `idempotentHint: false`. Every call appends to the server's thought history, and a call with both `branchFromThought` and `branchId` also appends to that branch, so the tool was never read-only or idempotent. `destructiveHint` and `openWorldHint` stay `false`. ([#4721](https://github.com/modelcontextprotocol/servers/issues/4721)) + +- [#4970](https://github.com/modelcontextprotocol/servers/pull/4970) [`86806cc`](https://github.com/modelcontextprotocol/servers/commit/86806cc3870a112f56870e86cd36bae8239edb02) Thanks [@cliffhall](https://github.com/cliffhall)! - Build the server with an exported `createServer()` factory and attach stdio only when `index.js` runs as the binary, so the server can be tested in-process. No change to the tool, its schemas or its results. + +- [#5031](https://github.com/modelcontextprotocol/servers/pull/5031) [`bc52d0e`](https://github.com/modelcontextprotocol/servers/commit/bc52d0e07a1d9dca505746165b30f4c99f8cc79b) Thanks [@cliffhall](https://github.com/cliffhall)! - The thought log on stderr no longer prints "undefined" in its headers: a revision with no `revisesThought` reads "Revision N/M", and a branch with no `branchId` reads "(from thought N)" with no ID. The box border is sized from the header's visible text, so colour escape codes no longer make it wider than its text. + +- [#5053](https://github.com/modelcontextprotocol/servers/pull/5053) [`c63255a`](https://github.com/modelcontextprotocol/servers/commit/c63255ae9616b110d6089fa343fcfdf3f5def534) Thanks [@cliffhall](https://github.com/cliffhall)! - Stop shipping the compiled test helper `dist/__tests__/helpers.js` in the published package: the build now excludes everything under `__tests__/`, not just `*.test.ts`. + +- [#5036](https://github.com/modelcontextprotocol/servers/pull/5036) [`b5c5ccc`](https://github.com/modelcontextprotocol/servers/commit/b5c5cccb79a5d43b41247065fb9efd174363e799) Thanks [@cliffhall](https://github.com/cliffhall)! - A `branchId` that names an `Object.prototype` key, such as `"constructor"` or `"__proto__"`, now creates and lists its branch like any other id instead of failing the call. A call that fails no longer adds its thought to the history first, so `thoughtHistoryLength` counts only thoughts that were accepted. From eb991bdb4b9ff01685fa353bf9d58324b0dda8af Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 01:17:37 -0400 Subject: [PATCH 47/65] chore: credit @cliffhall without the Thanks prefix in the 1.0.0 changelogs Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/everything/CHANGELOG.md | 18 +++++++++--------- src/filesystem/CHANGELOG.md | 16 ++++++++-------- src/memory/CHANGELOG.md | 16 ++++++++-------- src/sequentialthinking/CHANGELOG.md | 12 ++++++------ 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/everything/CHANGELOG.md b/src/everything/CHANGELOG.md index 5cf0063995..d21e889a5a 100644 --- a/src/everything/CHANGELOG.md +++ b/src/everything/CHANGELOG.md @@ -6,22 +6,22 @@ The first semantic version, resuming semver after the date-stamped releases (las ### Major Changes -- [#5063](https://github.com/modelcontextprotocol/servers/pull/5063) [`b5af226`](https://github.com/modelcontextprotocol/servers/commit/b5af22668592700c02236d737f9836c42654edb6) Thanks [@cliffhall](https://github.com/cliffhall)! - Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`, `@modelcontextprotocol/node`, and `@modelcontextprotocol/server-legacy` for the deprecated HTTP+SSE transport) from `@modelcontextprotocol/sdk` 1.x ([#4856](https://github.com/modelcontextprotocol/servers/issues/4856)). The task tools `simulate-research-query`, `trigger-sampling-request-async` and `trigger-elicitation-request-async` are removed, and the server no longer advertises the `tasks` capability, because SDK v2 dropped the experimental tasks layer they were built on; they return on the tasks extension in [#4852](https://github.com/modelcontextprotocol/servers/issues/4852). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. The optional `state` argument of `args-prompt` now carries its description. +- [#5063](https://github.com/modelcontextprotocol/servers/pull/5063) [`b5af226`](https://github.com/modelcontextprotocol/servers/commit/b5af22668592700c02236d737f9836c42654edb6) [@cliffhall](https://github.com/cliffhall) - Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`, `@modelcontextprotocol/node`, and `@modelcontextprotocol/server-legacy` for the deprecated HTTP+SSE transport) from `@modelcontextprotocol/sdk` 1.x ([#4856](https://github.com/modelcontextprotocol/servers/issues/4856)). The task tools `simulate-research-query`, `trigger-sampling-request-async` and `trigger-elicitation-request-async` are removed, and the server no longer advertises the `tasks` capability, because SDK v2 dropped the experimental tasks layer they were built on; they return on the tasks extension in [#4852](https://github.com/modelcontextprotocol/servers/issues/4852). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. The optional `state` argument of `args-prompt` now carries its description. ### Patch Changes -- [#5005](https://github.com/modelcontextprotocol/servers/pull/5005) [`63dc92f`](https://github.com/modelcontextprotocol/servers/commit/63dc92f0e95992faf9d3e339cb8a51b1090d138b) Thanks [@cliffhall](https://github.com/cliffhall)! - Label dynamic blob resources `application/octet-stream`, matching their resource template, instead of `text/plain`. This applies to `resources/read`, the `get-resource-reference` and `get-resource-links` tools, and the `resource-prompt` prompt; blob resource links are now described as "binary blob resource". +- [#5005](https://github.com/modelcontextprotocol/servers/pull/5005) [`63dc92f`](https://github.com/modelcontextprotocol/servers/commit/63dc92f0e95992faf9d3e339cb8a51b1090d138b) [@cliffhall](https://github.com/cliffhall) - Label dynamic blob resources `application/octet-stream`, matching their resource template, instead of `text/plain`. This applies to `resources/read`, the `get-resource-reference` and `get-resource-links` tools, and the `resource-prompt` prompt; blob resource links are now described as "binary blob resource". -- [#5034](https://github.com/modelcontextprotocol/servers/pull/5034) [`6bed4c5`](https://github.com/modelcontextprotocol/servers/commit/6bed4c5f7c219e77fe90b9e4b14cdb9f3f7e9503) Thanks [@cliffhall](https://github.com/cliffhall)! - Server instructions no longer tell every client to use tools that are registered only for clients declaring a capability; they list those tools and the capability each needs ([#4792](https://github.com/modelcontextprotocol/servers/issues/4792), ported from [#4835](https://github.com/modelcontextprotocol/servers/issues/4835)). `trigger-elicitation-request` is now registered only for clients that support form-mode elicitation, so a client that declared only URL-mode elicitation no longer sees a tool it cannot answer ([#4985](https://github.com/modelcontextprotocol/servers/issues/4985)). +- [#5034](https://github.com/modelcontextprotocol/servers/pull/5034) [`6bed4c5`](https://github.com/modelcontextprotocol/servers/commit/6bed4c5f7c219e77fe90b9e4b14cdb9f3f7e9503) [@cliffhall](https://github.com/cliffhall) - Server instructions no longer tell every client to use tools that are registered only for clients declaring a capability; they list those tools and the capability each needs ([#4792](https://github.com/modelcontextprotocol/servers/issues/4792), ported from [#4835](https://github.com/modelcontextprotocol/servers/issues/4835)). `trigger-elicitation-request` is now registered only for clients that support form-mode elicitation, so a client that declared only URL-mode elicitation no longer sees a tool it cannot answer ([#4985](https://github.com/modelcontextprotocol/servers/issues/4985)). -- [#5053](https://github.com/modelcontextprotocol/servers/pull/5053) [`c63255a`](https://github.com/modelcontextprotocol/servers/commit/c63255ae9616b110d6089fa343fcfdf3f5def534) Thanks [@cliffhall](https://github.com/cliffhall)! - Stop shipping the compiled test suite and Vitest config in the published package: the build now excludes `__tests__/`, `*.test.ts`, `*.spec.ts` and `vitest.config.ts`, as the other TypeScript servers' builds do. +- [#5053](https://github.com/modelcontextprotocol/servers/pull/5053) [`c63255a`](https://github.com/modelcontextprotocol/servers/commit/c63255ae9616b110d6089fa343fcfdf3f5def534) [@cliffhall](https://github.com/cliffhall) - Stop shipping the compiled test suite and Vitest config in the published package: the build now excludes `__tests__/`, `*.test.ts`, `*.spec.ts` and `vitest.config.ts`, as the other TypeScript servers' builds do. -- [#5032](https://github.com/modelcontextprotocol/servers/pull/5032) [`d4fb2f4`](https://github.com/modelcontextprotocol/servers/commit/d4fb2f4f11efc084d97958dbecefa12c03020bd9) Thanks [@cliffhall](https://github.com/cliffhall)! - Session resources are now tracked per server, so two sessions that create a session resource with the same name (for example via `gzip-file-as-resource`) no longer evict each other's resource ([#4808](https://github.com/modelcontextprotocol/servers/issues/4808)). +- [#5032](https://github.com/modelcontextprotocol/servers/pull/5032) [`d4fb2f4`](https://github.com/modelcontextprotocol/servers/commit/d4fb2f4f11efc084d97958dbecefa12c03020bd9) [@cliffhall](https://github.com/cliffhall) - Session resources are now tracked per server, so two sessions that create a session resource with the same name (for example via `gzip-file-as-resource`) no longer evict each other's resource ([#4808](https://github.com/modelcontextprotocol/servers/issues/4808)). -- [#5020](https://github.com/modelcontextprotocol/servers/pull/5020) [`f7af617`](https://github.com/modelcontextprotocol/servers/commit/f7af617ead2d4c7faf10338159a280d6387a1475) Thanks [@cliffhall](https://github.com/cliffhall)! - The HTTP+SSE transport now answers requests for sessions it cannot serve instead of leaving them hanging or failing with a 500: `POST /message` for an unknown session returns `404` (and `400` with no `sessionId`), and `GET /sse?sessionId=…` returns `409` for a session that already has its stream and `404` for an unknown one. Each carries a JSON-RPC error body. +- [#5020](https://github.com/modelcontextprotocol/servers/pull/5020) [`f7af617`](https://github.com/modelcontextprotocol/servers/commit/f7af617ead2d4c7faf10338159a280d6387a1475) [@cliffhall](https://github.com/cliffhall) - The HTTP+SSE transport now answers requests for sessions it cannot serve instead of leaving them hanging or failing with a 500: `POST /message` for an unknown session returns `404` (and `400` with no `sessionId`), and `GET /sse?sessionId=…` returns `409` for a session that already has its stream and `404` for an unknown one. Each carries a JSON-RPC error body. -- [#5006](https://github.com/modelcontextprotocol/servers/pull/5006) [`dbd1b2e`](https://github.com/modelcontextprotocol/servers/commit/dbd1b2e699714074c6355ddf60e1fb832f139d7f) Thanks [@cliffhall](https://github.com/cliffhall)! - Streamable HTTP: answer an unknown or ended session ID with `404 Not Found` (carrying the request's `id` on a POST) instead of `400`; replay only the resumed stream's events after a `Last-Event-ID`, and refuse an unknown one; close every open session on shutdown. +- [#5006](https://github.com/modelcontextprotocol/servers/pull/5006) [`dbd1b2e`](https://github.com/modelcontextprotocol/servers/commit/dbd1b2e699714074c6355ddf60e1fb832f139d7f) [@cliffhall](https://github.com/cliffhall) - Streamable HTTP: answer an unknown or ended session ID with `404 Not Found` (carrying the request's `id` on a POST) instead of `400`; replay only the resumed stream's events after a `Last-Event-ID`, and refuse an unknown one; close every open session on shutdown. -- [#4970](https://github.com/modelcontextprotocol/servers/pull/4970) [`c449b5e`](https://github.com/modelcontextprotocol/servers/commit/c449b5e71b7c9cc926a39799a2dcf0ec401d3a20) Thanks [@cliffhall](https://github.com/cliffhall)! - Internal refactor for in-process testing, with no change in behavior: the launcher only starts a transport when run as the binary, and the stdio, SSE and Streamable HTTP transport modules export their start-up (`startStdioServer`, `startSseServer`, `startStreamableHttpServer`, and `createApp()` for the HTTP transports) instead of starting a server when imported. +- [#4970](https://github.com/modelcontextprotocol/servers/pull/4970) [`c449b5e`](https://github.com/modelcontextprotocol/servers/commit/c449b5e71b7c9cc926a39799a2dcf0ec401d3a20) [@cliffhall](https://github.com/cliffhall) - Internal refactor for in-process testing, with no change in behavior: the launcher only starts a transport when run as the binary, and the stdio, SSE and Streamable HTTP transport modules export their start-up (`startStdioServer`, `startSseServer`, `startStreamableHttpServer`, and `createApp()` for the HTTP transports) instead of starting a server when imported. -- [#4936](https://github.com/modelcontextprotocol/servers/pull/4936) [`feb251d`](https://github.com/modelcontextprotocol/servers/commit/feb251d2fd0057570125ba0ba25e98c84f8c5b5c) Thanks [@cliffhall](https://github.com/cliffhall)! - The HTTP+SSE transport no longer prints "Server is running" and stays up when its port is already in use: it reports the port and exits non-zero, as Streamable HTTP does. Streamable HTTP no longer prints its listening line before that error. +- [#4936](https://github.com/modelcontextprotocol/servers/pull/4936) [`feb251d`](https://github.com/modelcontextprotocol/servers/commit/feb251d2fd0057570125ba0ba25e98c84f8c5b5c) [@cliffhall](https://github.com/cliffhall) - The HTTP+SSE transport no longer prints "Server is running" and stays up when its port is already in use: it reports the port and exits non-zero, as Streamable HTTP does. Streamable HTTP no longer prints its listening line before that error. diff --git a/src/filesystem/CHANGELOG.md b/src/filesystem/CHANGELOG.md index bd71316b1a..e81be2302c 100644 --- a/src/filesystem/CHANGELOG.md +++ b/src/filesystem/CHANGELOG.md @@ -6,20 +6,20 @@ The first semantic version, resuming semver after the date-stamped releases (las ### Major Changes -- [#5063](https://github.com/modelcontextprotocol/servers/pull/5063) [`b5af226`](https://github.com/modelcontextprotocol/servers/commit/b5af22668592700c02236d737f9836c42654edb6) Thanks [@cliffhall](https://github.com/cliffhall)! - Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x ([#4856](https://github.com/modelcontextprotocol/servers/issues/4856)). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); and tools no longer advertise `execution.taskSupport`. +- [#5063](https://github.com/modelcontextprotocol/servers/pull/5063) [`b5af226`](https://github.com/modelcontextprotocol/servers/commit/b5af22668592700c02236d737f9836c42654edb6) [@cliffhall](https://github.com/cliffhall) - Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x ([#4856](https://github.com/modelcontextprotocol/servers/issues/4856)). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); and tools no longer advertise `execution.taskSupport`. ### Patch Changes -- [#4970](https://github.com/modelcontextprotocol/servers/pull/4970) [`3a63f52`](https://github.com/modelcontextprotocol/servers/commit/3a63f52e751882e20b24e8f0c840d402348bbd72) Thanks [@cliffhall](https://github.com/cliffhall)! - Internal: the server is now built by a `createServer()` factory in `server.ts`, and `index.ts` starts it over stdio only when run as the bin, with each server instance holding its own allowed directories. No change to the tools, their results, or how the allowed directories are resolved from arguments and Roots. +- [#4970](https://github.com/modelcontextprotocol/servers/pull/4970) [`3a63f52`](https://github.com/modelcontextprotocol/servers/commit/3a63f52e751882e20b24e8f0c840d402348bbd72) [@cliffhall](https://github.com/cliffhall) - Internal: the server is now built by a `createServer()` factory in `server.ts`, and `index.ts` starts it over stdio only when run as the bin, with each server instance holding its own allowed directories. No change to the tools, their results, or how the allowed directories are resolved from arguments and Roots. -- [#5011](https://github.com/modelcontextprotocol/servers/pull/5011) [`d8ebdbe`](https://github.com/modelcontextprotocol/servers/commit/d8ebdbef770768c05d9fddbb663bc6f380a08867) Thanks [@cliffhall](https://github.com/cliffhall)! - `edit_file` keeps a file's line endings: a CRLF file is written back with CRLF instead of being converted to LF. When `oldText` has no exact match and the whitespace-tolerant matcher is used, each replacement line now takes the indentation of the file line it replaces, shifted by its indentation relative to the matching `oldText` line, instead of only the first line being reindented. +- [#5011](https://github.com/modelcontextprotocol/servers/pull/5011) [`d8ebdbe`](https://github.com/modelcontextprotocol/servers/commit/d8ebdbef770768c05d9fddbb663bc6f380a08867) [@cliffhall](https://github.com/cliffhall) - `edit_file` keeps a file's line endings: a CRLF file is written back with CRLF instead of being converted to LF. When `oldText` has no exact match and the whitespace-tolerant matcher is used, each replacement line now takes the indentation of the file line it replaces, shifted by its indentation relative to the matching `oldText` line, instead of only the first line being reindented. -- [#5022](https://github.com/modelcontextprotocol/servers/pull/5022) [`7dc802f`](https://github.com/modelcontextprotocol/servers/commit/7dc802f914a727e840b65712b0a9fbaf7fe1c2b5) Thanks [@cliffhall](https://github.com/cliffhall)! - `write_file` and `edit_file` now overwrite an existing file in place instead of renaming a temp file over it, so the file keeps its inode, creation time (birthtime), hard links and permission bits ([#4512](https://github.com/modelcontextprotocol/servers/issues/4512)), and an overwrite no longer fails with `EPERM` on Windows when another process holds the file open ([#3199](https://github.com/modelcontextprotocol/servers/issues/3199)). A symlink swapped in after the path was validated is still refused rather than written through, now by an `O_NOFOLLOW` open (on POSIX) and a check that the opened file is the one validated (on every platform). The overwrite is no longer crash-atomic. A read-only file is now refused (`EACCES`, or `EPERM` on Windows) instead of being replaced, since writing in place opens the file itself for writing. +- [#5022](https://github.com/modelcontextprotocol/servers/pull/5022) [`7dc802f`](https://github.com/modelcontextprotocol/servers/commit/7dc802f914a727e840b65712b0a9fbaf7fe1c2b5) [@cliffhall](https://github.com/cliffhall) - `write_file` and `edit_file` now overwrite an existing file in place instead of renaming a temp file over it, so the file keeps its inode, creation time (birthtime), hard links and permission bits ([#4512](https://github.com/modelcontextprotocol/servers/issues/4512)), and an overwrite no longer fails with `EPERM` on Windows when another process holds the file open ([#3199](https://github.com/modelcontextprotocol/servers/issues/3199)). A symlink swapped in after the path was validated is still refused rather than written through, now by an `O_NOFOLLOW` open (on POSIX) and a check that the opened file is the one validated (on every platform). The overwrite is no longer crash-atomic. A read-only file is now refused (`EACCES`, or `EPERM` on Windows) instead of being replaced, since writing in place opens the file itself for writing. -- [#5025](https://github.com/modelcontextprotocol/servers/pull/5025) [`2309e08`](https://github.com/modelcontextprotocol/servers/commit/2309e089b12385650e52784e26668aabdbc9b74b) Thanks [@cliffhall](https://github.com/cliffhall)! - Tool calls now wait for the client's initial roots before checking paths, so a call sent right after connecting is no longer refused with "Access denied" ([#3204](https://github.com/modelcontextprotocol/servers/issues/3204)). A server started with no directories, connected to a client without Roots support, now fails visibly: it logs the reason, closes the connection and exits with status 1, instead of staying up and refusing every call ([#4992](https://github.com/modelcontextprotocol/servers/issues/4992)). +- [#5025](https://github.com/modelcontextprotocol/servers/pull/5025) [`2309e08`](https://github.com/modelcontextprotocol/servers/commit/2309e089b12385650e52784e26668aabdbc9b74b) [@cliffhall](https://github.com/cliffhall) - Tool calls now wait for the client's initial roots before checking paths, so a call sent right after connecting is no longer refused with "Access denied" ([#3204](https://github.com/modelcontextprotocol/servers/issues/3204)). A server started with no directories, connected to a client without Roots support, now fails visibly: it logs the reason, closes the connection and exits with status 1, instead of staying up and refusing every call ([#4992](https://github.com/modelcontextprotocol/servers/issues/4992)). -- [#5030](https://github.com/modelcontextprotocol/servers/pull/5030) [`5c04d74`](https://github.com/modelcontextprotocol/servers/commit/5c04d74ff6430ae82763aae76b3b1e585f5f13fd) Thanks [@cliffhall](https://github.com/cliffhall)! - Every tool's input and output schema in `tools/list` now declares `"$schema": "https://json-schema.org/draft/2020-12/schema"` instead of draft-07, so clients that validate tool schemas strictly against JSON Schema 2020-12 no longer reject every tool. The schemas are otherwise unchanged. +- [#5030](https://github.com/modelcontextprotocol/servers/pull/5030) [`5c04d74`](https://github.com/modelcontextprotocol/servers/commit/5c04d74ff6430ae82763aae76b3b1e585f5f13fd) [@cliffhall](https://github.com/cliffhall) - Every tool's input and output schema in `tools/list` now declares `"$schema": "https://json-schema.org/draft/2020-12/schema"` instead of draft-07, so clients that validate tool schemas strictly against JSON Schema 2020-12 no longer reject every tool. The schemas are otherwise unchanged. -- [#5010](https://github.com/modelcontextprotocol/servers/pull/5010) [`fd4dff7`](https://github.com/modelcontextprotocol/servers/commit/fd4dff7da877068734bee9419fa935213d66e359) Thanks [@cliffhall](https://github.com/cliffhall)! - On Windows, a UNC share root given as an allowed directory (`\\server\share` or `\\server\share\`) now admits the files and subdirectories under it. Before, only the share root itself was accessible and every path below it was refused. Sibling shares such as `\\server\share-evil` are still refused. +- [#5010](https://github.com/modelcontextprotocol/servers/pull/5010) [`fd4dff7`](https://github.com/modelcontextprotocol/servers/commit/fd4dff7da877068734bee9419fa935213d66e359) [@cliffhall](https://github.com/cliffhall) - On Windows, a UNC share root given as an allowed directory (`\\server\share` or `\\server\share\`) now admits the files and subdirectories under it. Before, only the share root itself was accessible and every path below it was refused. Sibling shares such as `\\server\share-evil` are still refused. -- [#5009](https://github.com/modelcontextprotocol/servers/pull/5009) [`f621c75`](https://github.com/modelcontextprotocol/servers/commit/f621c75a92347738f28f26c9461286764beba3c8) Thanks [@cliffhall](https://github.com/cliffhall)! - Paths that differ from the names on disk only in Unicode form now resolve ([#1970](https://github.com/modelcontextprotocol/servers/issues/1970)). An NFC spelling of an allowed directory whose name is stored NFD (macOS "Capture d’écran", Japanese dakuten) is no longer refused as outside the allowed directories, and a request that spells a U+202F or U+00A0 in a name as a plain space (the macOS screenshot "Screenshot … at 2.40.40 PM.png") finds the file instead of failing with ENOENT. The exact spelling still wins when it exists, and a name matching two entries is refused as ambiguous. +- [#5009](https://github.com/modelcontextprotocol/servers/pull/5009) [`f621c75`](https://github.com/modelcontextprotocol/servers/commit/f621c75a92347738f28f26c9461286764beba3c8) [@cliffhall](https://github.com/cliffhall) - Paths that differ from the names on disk only in Unicode form now resolve ([#1970](https://github.com/modelcontextprotocol/servers/issues/1970)). An NFC spelling of an allowed directory whose name is stored NFD (macOS "Capture d’écran", Japanese dakuten) is no longer refused as outside the allowed directories, and a request that spells a U+202F or U+00A0 in a name as a plain space (the macOS screenshot "Screenshot … at 2.40.40 PM.png") finds the file instead of failing with ENOENT. The exact spelling still wins when it exists, and a name matching two entries is refused as ambiguous. diff --git a/src/memory/CHANGELOG.md b/src/memory/CHANGELOG.md index 30d1530203..81c2482f3d 100644 --- a/src/memory/CHANGELOG.md +++ b/src/memory/CHANGELOG.md @@ -6,20 +6,20 @@ The first semantic version, resuming semver after the date-stamped releases (las ### Major Changes -- [#5063](https://github.com/modelcontextprotocol/servers/pull/5063) [`b5af226`](https://github.com/modelcontextprotocol/servers/commit/b5af22668592700c02236d737f9836c42654edb6) Thanks [@cliffhall](https://github.com/cliffhall)! - Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x ([#4856](https://github.com/modelcontextprotocol/servers/issues/4856)). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. +- [#5063](https://github.com/modelcontextprotocol/servers/pull/5063) [`b5af226`](https://github.com/modelcontextprotocol/servers/commit/b5af22668592700c02236d737f9836c42654edb6) [@cliffhall](https://github.com/cliffhall) - Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x ([#4856](https://github.com/modelcontextprotocol/servers/issues/4856)). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. ### Patch Changes -- [#5008](https://github.com/modelcontextprotocol/servers/pull/5008) [`bb8aa6f`](https://github.com/modelcontextprotocol/servers/commit/bb8aa6fd73b2a2fd68bc7084292296d2b0084780) Thanks [@cliffhall](https://github.com/cliffhall)! - `create_entities` now reports the entities it skipped because their name already exists (or repeats earlier in the same call): the structured result lists them in a new optional `skipped` array, a second text item names them and points to `add_observations`, and the tool description says so. Skipped entities are still not created and their observations are still not added ([#4887](https://github.com/modelcontextprotocol/servers/issues/4887)). +- [#5008](https://github.com/modelcontextprotocol/servers/pull/5008) [`bb8aa6f`](https://github.com/modelcontextprotocol/servers/commit/bb8aa6fd73b2a2fd68bc7084292296d2b0084780) [@cliffhall](https://github.com/cliffhall) - `create_entities` now reports the entities it skipped because their name already exists (or repeats earlier in the same call): the structured result lists them in a new optional `skipped` array, a second text item names them and points to `add_observations`, and the tool description says so. Skipped entities are still not created and their observations are still not added ([#4887](https://github.com/modelcontextprotocol/servers/issues/4887)). -- [#4970](https://github.com/modelcontextprotocol/servers/pull/4970) [`a8127ba`](https://github.com/modelcontextprotocol/servers/commit/a8127ba956c12c7f6597b43765f1f7a68aee0d7e) Thanks [@cliffhall](https://github.com/cliffhall)! - Build the server with an exported `createServer()` factory and start stdio only when the package is run as a program, so importing the module no longer starts a server. Behavior over stdio is unchanged. +- [#4970](https://github.com/modelcontextprotocol/servers/pull/4970) [`a8127ba`](https://github.com/modelcontextprotocol/servers/commit/a8127ba956c12c7f6597b43765f1f7a68aee0d7e) [@cliffhall](https://github.com/cliffhall) - Build the server with an exported `createServer()` factory and start stdio only when the package is run as a program, so importing the module no longer starts a server. Behavior over stdio is unchanged. -- [#5037](https://github.com/modelcontextprotocol/servers/pull/5037) [`7be08e9`](https://github.com/modelcontextprotocol/servers/commit/7be08e97f399721c215de52bfb74a5030c20f78f) Thanks [@cliffhall](https://github.com/cliffhall)! - Two server processes sharing one memory file no longer silently discard each other's writes: every write tool now holds an exclusive lock file (`.lock`) while it reads, changes and saves the graph, and a lock left by a crashed server is recovered automatically ([#4797](https://github.com/modelcontextprotocol/servers/issues/4797)). +- [#5037](https://github.com/modelcontextprotocol/servers/pull/5037) [`7be08e9`](https://github.com/modelcontextprotocol/servers/commit/7be08e97f399721c215de52bfb74a5030c20f78f) [@cliffhall](https://github.com/cliffhall) - Two server processes sharing one memory file no longer silently discard each other's writes: every write tool now holds an exclusive lock file (`.lock`) while it reads, changes and saves the graph, and a lock left by a crashed server is recovered automatically ([#4797](https://github.com/modelcontextprotocol/servers/issues/4797)). -- [#4937](https://github.com/modelcontextprotocol/servers/pull/4937) [`478db6a`](https://github.com/modelcontextprotocol/servers/commit/478db6a5d5bc0bec3196e2c8c9461ec1597ded34) Thanks [@cliffhall](https://github.com/cliffhall)! - Internal: `ensureMemoryFilePath` accepts the directory its default files live in, so the server's tests no longer write into the package directory. No change to how the server chooses or migrates its memory file. +- [#4937](https://github.com/modelcontextprotocol/servers/pull/4937) [`478db6a`](https://github.com/modelcontextprotocol/servers/commit/478db6a5d5bc0bec3196e2c8c9461ec1597ded34) [@cliffhall](https://github.com/cliffhall) - Internal: `ensureMemoryFilePath` accepts the directory its default files live in, so the server's tests no longer write into the package directory. No change to how the server chooses or migrates its memory file. -- [#5035](https://github.com/modelcontextprotocol/servers/pull/5035) [`2b1305f`](https://github.com/modelcontextprotocol/servers/commit/2b1305fc0db794cdf1bd57ca83d13fb8a195e869) Thanks [@cliffhall](https://github.com/cliffhall)! - Lines in the memory file that the server cannot read (malformed JSON, an entity or relation that fails validation, an unknown record type) are no longer deleted by the next write. They are still left out of the graph, and are now written back unchanged after the graph's own lines. +- [#5035](https://github.com/modelcontextprotocol/servers/pull/5035) [`2b1305f`](https://github.com/modelcontextprotocol/servers/commit/2b1305fc0db794cdf1bd57ca83d13fb8a195e869) [@cliffhall](https://github.com/cliffhall) - Lines in the memory file that the server cannot read (malformed JSON, an entity or relation that fails validation, an unknown record type) are no longer deleted by the next write. They are still left out of the graph, and are now written back unchanged after the graph's own lines. -- [#5053](https://github.com/modelcontextprotocol/servers/pull/5053) [`c63255a`](https://github.com/modelcontextprotocol/servers/commit/c63255ae9616b110d6089fa343fcfdf3f5def534) Thanks [@cliffhall](https://github.com/cliffhall)! - Stop shipping the compiled test helper `dist/__tests__/helpers.js` in the published package: the build now excludes everything under `__tests__/`, not just `*.test.ts`. +- [#5053](https://github.com/modelcontextprotocol/servers/pull/5053) [`c63255a`](https://github.com/modelcontextprotocol/servers/commit/c63255ae9616b110d6089fa343fcfdf3f5def534) [@cliffhall](https://github.com/cliffhall) - Stop shipping the compiled test helper `dist/__tests__/helpers.js` in the published package: the build now excludes everything under `__tests__/`, not just `*.test.ts`. -- [#5013](https://github.com/modelcontextprotocol/servers/pull/5013) [`819ae90`](https://github.com/modelcontextprotocol/servers/commit/819ae904cc08ab0b208ccb91addc3169b3ece85b) Thanks [@cliffhall](https://github.com/cliffhall)! - Saving the knowledge graph keeps the memory file's permission bits (an operator's `0600` no longer comes back `0644`), and a write to a read-only memory file now fails with `EACCES` instead of silently replacing it ([#4827](https://github.com/modelcontextprotocol/servers/issues/4827)). +- [#5013](https://github.com/modelcontextprotocol/servers/pull/5013) [`819ae90`](https://github.com/modelcontextprotocol/servers/commit/819ae904cc08ab0b208ccb91addc3169b3ece85b) [@cliffhall](https://github.com/cliffhall) - Saving the knowledge graph keeps the memory file's permission bits (an operator's `0600` no longer comes back `0644`), and a write to a read-only memory file now fails with `EACCES` instead of silently replacing it ([#4827](https://github.com/modelcontextprotocol/servers/issues/4827)). diff --git a/src/sequentialthinking/CHANGELOG.md b/src/sequentialthinking/CHANGELOG.md index 352bd55a4b..ed28422843 100644 --- a/src/sequentialthinking/CHANGELOG.md +++ b/src/sequentialthinking/CHANGELOG.md @@ -6,16 +6,16 @@ The first semantic version, resuming semver after the date-stamped releases (las ### Major Changes -- [#5063](https://github.com/modelcontextprotocol/servers/pull/5063) [`b5af226`](https://github.com/modelcontextprotocol/servers/commit/b5af22668592700c02236d737f9836c42654edb6) Thanks [@cliffhall](https://github.com/cliffhall)! - Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x ([#4856](https://github.com/modelcontextprotocol/servers/issues/4856)). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. +- [#5063](https://github.com/modelcontextprotocol/servers/pull/5063) [`b5af226`](https://github.com/modelcontextprotocol/servers/commit/b5af22668592700c02236d737f9836c42654edb6) [@cliffhall](https://github.com/cliffhall) - Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x ([#4856](https://github.com/modelcontextprotocol/servers/issues/4856)). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. ### Patch Changes -- [#5015](https://github.com/modelcontextprotocol/servers/pull/5015) [`8a4139c`](https://github.com/modelcontextprotocol/servers/commit/8a4139c3d71b66e95d2f7cd2bc485d4781ecf1d7) Thanks [@cliffhall](https://github.com/cliffhall)! - Advertise the `sequentialthinking` tool as `readOnlyHint: false` and `idempotentHint: false`. Every call appends to the server's thought history, and a call with both `branchFromThought` and `branchId` also appends to that branch, so the tool was never read-only or idempotent. `destructiveHint` and `openWorldHint` stay `false`. ([#4721](https://github.com/modelcontextprotocol/servers/issues/4721)) +- [#5015](https://github.com/modelcontextprotocol/servers/pull/5015) [`8a4139c`](https://github.com/modelcontextprotocol/servers/commit/8a4139c3d71b66e95d2f7cd2bc485d4781ecf1d7) [@cliffhall](https://github.com/cliffhall) - Advertise the `sequentialthinking` tool as `readOnlyHint: false` and `idempotentHint: false`. Every call appends to the server's thought history, and a call with both `branchFromThought` and `branchId` also appends to that branch, so the tool was never read-only or idempotent. `destructiveHint` and `openWorldHint` stay `false`. ([#4721](https://github.com/modelcontextprotocol/servers/issues/4721)) -- [#4970](https://github.com/modelcontextprotocol/servers/pull/4970) [`86806cc`](https://github.com/modelcontextprotocol/servers/commit/86806cc3870a112f56870e86cd36bae8239edb02) Thanks [@cliffhall](https://github.com/cliffhall)! - Build the server with an exported `createServer()` factory and attach stdio only when `index.js` runs as the binary, so the server can be tested in-process. No change to the tool, its schemas or its results. +- [#4970](https://github.com/modelcontextprotocol/servers/pull/4970) [`86806cc`](https://github.com/modelcontextprotocol/servers/commit/86806cc3870a112f56870e86cd36bae8239edb02) [@cliffhall](https://github.com/cliffhall) - Build the server with an exported `createServer()` factory and attach stdio only when `index.js` runs as the binary, so the server can be tested in-process. No change to the tool, its schemas or its results. -- [#5031](https://github.com/modelcontextprotocol/servers/pull/5031) [`bc52d0e`](https://github.com/modelcontextprotocol/servers/commit/bc52d0e07a1d9dca505746165b30f4c99f8cc79b) Thanks [@cliffhall](https://github.com/cliffhall)! - The thought log on stderr no longer prints "undefined" in its headers: a revision with no `revisesThought` reads "Revision N/M", and a branch with no `branchId` reads "(from thought N)" with no ID. The box border is sized from the header's visible text, so colour escape codes no longer make it wider than its text. +- [#5031](https://github.com/modelcontextprotocol/servers/pull/5031) [`bc52d0e`](https://github.com/modelcontextprotocol/servers/commit/bc52d0e07a1d9dca505746165b30f4c99f8cc79b) [@cliffhall](https://github.com/cliffhall) - The thought log on stderr no longer prints "undefined" in its headers: a revision with no `revisesThought` reads "Revision N/M", and a branch with no `branchId` reads "(from thought N)" with no ID. The box border is sized from the header's visible text, so colour escape codes no longer make it wider than its text. -- [#5053](https://github.com/modelcontextprotocol/servers/pull/5053) [`c63255a`](https://github.com/modelcontextprotocol/servers/commit/c63255ae9616b110d6089fa343fcfdf3f5def534) Thanks [@cliffhall](https://github.com/cliffhall)! - Stop shipping the compiled test helper `dist/__tests__/helpers.js` in the published package: the build now excludes everything under `__tests__/`, not just `*.test.ts`. +- [#5053](https://github.com/modelcontextprotocol/servers/pull/5053) [`c63255a`](https://github.com/modelcontextprotocol/servers/commit/c63255ae9616b110d6089fa343fcfdf3f5def534) [@cliffhall](https://github.com/cliffhall) - Stop shipping the compiled test helper `dist/__tests__/helpers.js` in the published package: the build now excludes everything under `__tests__/`, not just `*.test.ts`. -- [#5036](https://github.com/modelcontextprotocol/servers/pull/5036) [`b5c5ccc`](https://github.com/modelcontextprotocol/servers/commit/b5c5cccb79a5d43b41247065fb9efd174363e799) Thanks [@cliffhall](https://github.com/cliffhall)! - A `branchId` that names an `Object.prototype` key, such as `"constructor"` or `"__proto__"`, now creates and lists its branch like any other id instead of failing the call. A call that fails no longer adds its thought to the history first, so `thoughtHistoryLength` counts only thoughts that were accepted. +- [#5036](https://github.com/modelcontextprotocol/servers/pull/5036) [`b5c5ccc`](https://github.com/modelcontextprotocol/servers/commit/b5c5cccb79a5d43b41247065fb9efd174363e799) [@cliffhall](https://github.com/cliffhall) - A `branchId` that names an `Object.prototype` key, such as `"constructor"` or `"__proto__"`, now creates and lists its branch like any other id instead of failing the call. A call that fails no longer adds its thought to the history first, so `thoughtHistoryLength` counts only thoughts that were accepted. From 3423e578031c554e9aefa4d648e0d05d6f0be917 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 01:22:24 -0400 Subject: [PATCH 48/65] chore: stamp Python CalVer versions for 2026.10.10 Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/fetch/pyproject.toml | 2 +- src/fetch/uv.lock | 2 +- src/git/pyproject.toml | 2 +- src/git/uv.lock | 2 +- src/time/pyproject.toml | 2 +- src/time/uv.lock | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/fetch/pyproject.toml b/src/fetch/pyproject.toml index 2f596c278a..c629084d5a 100644 --- a/src/fetch/pyproject.toml +++ b/src/fetch/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mcp-server-fetch" -version = "0.6.3" +version = "2026.10.10" description = "A Model Context Protocol server providing tools to fetch and convert web content for usage by LLMs" readme = "README.md" requires-python = ">=3.10" diff --git a/src/fetch/uv.lock b/src/fetch/uv.lock index 2b9d970293..771fcb39f4 100644 --- a/src/fetch/uv.lock +++ b/src/fetch/uv.lock @@ -745,7 +745,7 @@ wheels = [ [[package]] name = "mcp-server-fetch" -version = "0.6.3" +version = "2026.10.10" source = { editable = "." } dependencies = [ { name = "httpx", extra = ["socks"] }, diff --git a/src/git/pyproject.toml b/src/git/pyproject.toml index d9fda96b55..7036291e80 100644 --- a/src/git/pyproject.toml +++ b/src/git/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mcp-server-git" -version = "0.6.2" +version = "2026.10.10" description = "A Model Context Protocol server providing tools to read, search, and manipulate Git repositories programmatically via LLMs" readme = "README.md" requires-python = ">=3.10" diff --git a/src/git/uv.lock b/src/git/uv.lock index 39bec35e34..0ed114edd3 100644 --- a/src/git/uv.lock +++ b/src/git/uv.lock @@ -509,7 +509,7 @@ wheels = [ [[package]] name = "mcp-server-git" -version = "0.6.2" +version = "2026.10.10" source = { editable = "." } dependencies = [ { name = "click" }, diff --git a/src/time/pyproject.toml b/src/time/pyproject.toml index 014dbf629e..d37e27724e 100644 --- a/src/time/pyproject.toml +++ b/src/time/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mcp-server-time" -version = "0.6.2" +version = "2026.10.10" description = "A Model Context Protocol server providing tools for time queries and timezone conversions for LLMs" readme = "README.md" requires-python = ">=3.10" diff --git a/src/time/uv.lock b/src/time/uv.lock index fdb92e13d1..16584d44cd 100644 --- a/src/time/uv.lock +++ b/src/time/uv.lock @@ -497,7 +497,7 @@ wheels = [ [[package]] name = "mcp-server-time" -version = "0.6.2" +version = "2026.10.10" source = { editable = "." } dependencies = [ { name = "jsonschema" }, From 211aa1ee36f6b8f003557db58b73810bde94c395 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 02:34:26 -0400 Subject: [PATCH 49/65] docs(release): warn against Update branch on the milestone merge PR GitHub always offers Update branch on the merge PR, since main holds commits the merge branch lacks. Clicking it adds a merge of main to the branch, so the PR is no longer a pure merge. Says so next to the merge-commit warning, with the recovery. Closes #5092 Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .claude/skills/release/SKILL.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 0b07c4b407..0a0bde684f 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -258,6 +258,20 @@ squash writes one new commit that `v2/main` does not contain, so the two branches stop sharing history and the next milestone merge conflicts with itself. +⚠️ **Do not click "Update branch" on the merge PR.** GitHub always offers it +here, because `main` always holds commits the merge branch lacks (its own +earlier merge commits), so it does not mean the PR is out of date. Clicking it +merges `main` into the merge branch: the PR gains a commit of its own, `main` +receives two merge commits, and the branch head is no longer the commit the +ledger names. If it was clicked, put the branch back on the commit it was cut +from, then repeat the `git merge-tree` check above: + +```sh +git fetch origin +git push --force-with-lease="v2/chore/$N-release-$MILESTONE" origin \ + "origin/v2/main:refs/heads/v2/chore/$N-release-$MILESTONE" +``` + ⚠️ **Never merge `main` back into `v2/main`** to "sync" them. Everything reaches `main` through `v2/main`, so there is nothing on `main` to bring back except the merge commits themselves. `v2/main` ahead of `main` means a release From aa7fa785c7c746e2fc52de6ffeb988847d38d93e Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 02:40:03 -0400 Subject: [PATCH 50/65] docs(release): reset to the update merge's first parent, not origin/v2/main origin/v2/main may have moved past the commit the ledger verified; the update merge's first parent is the branch head from before the click. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .claude/skills/release/SKILL.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 0a0bde684f..0a8ffb8a07 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -263,13 +263,17 @@ here, because `main` always holds commits the merge branch lacks (its own earlier merge commits), so it does not mean the PR is out of date. Clicking it merges `main` into the merge branch: the PR gains a commit of its own, `main` receives two merge commits, and the branch head is no longer the commit the -ledger names. If it was clicked, put the branch back on the commit it was cut -from, then repeat the `git merge-tree` check above: +ledger names. If it was clicked, put the branch back on its head from before +the click: the update merge's **first parent**, which is the commit the ledger +verified. Not `origin/v2/main`, which may have moved on to a commit nothing has +verified. Then repeat the `git merge-tree` check above: ```sh +B="v2/chore/$N-release-$MILESTONE" git fetch origin -git push --force-with-lease="v2/chore/$N-release-$MILESTONE" origin \ - "origin/v2/main:refs/heads/v2/chore/$N-release-$MILESTONE" +UPDATE=$(git rev-parse "origin/$B") +git log -1 --format='%s' "$UPDATE" # must be "Merge branch 'main' into …" +git push --force-with-lease="$B:$UPDATE" origin "$UPDATE^1:refs/heads/$B" ``` ⚠️ **Never merge `main` back into `v2/main`** to "sync" them. Everything From 5ea217b57e042714c6cb29bb03502ce1c6d638c4 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 02:56:35 -0400 Subject: [PATCH 51/65] fix(filesystem): revoke access when a roots update leaves no valid root Once the client's roots are in force, a roots/list_changed update with no valid roots now clears the allow-list instead of keeping the previous directories. Before any root has been in force, such an update keeps the command-line directories, as before. An empty allow-list now says so in its access-denied error. Closes #5094 Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .changeset/filesystem-revoke-empty-roots.md | 5 + src/filesystem/README.md | 1 + src/filesystem/__tests__/server-paths.test.ts | 2 +- src/filesystem/__tests__/server-roots.test.ts | 94 +++++++++++++++++++ src/filesystem/lib.ts | 4 +- src/filesystem/server.ts | 11 +++ 6 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 .changeset/filesystem-revoke-empty-roots.md diff --git a/.changeset/filesystem-revoke-empty-roots.md b/.changeset/filesystem-revoke-empty-roots.md new file mode 100644 index 0000000000..f563e3373a --- /dev/null +++ b/.changeset/filesystem-revoke-empty-roots.md @@ -0,0 +1,5 @@ +--- +"@modelcontextprotocol/server-filesystem": patch +--- + +A `roots/list_changed` update that leaves no valid root now revokes access once the client's roots are in force, instead of keeping the previous allowed directories, so a client that withdraws its roots withdraws the server's access too (#5094). Every path is then refused with "Access denied - no allowed directories" until the client exposes a root again. Before any root has been in force, such an update still keeps the command-line directories. diff --git a/src/filesystem/README.md b/src/filesystem/README.md index 8b705087dc..056449ac86 100644 --- a/src/filesystem/README.md +++ b/src/filesystem/README.md @@ -51,6 +51,7 @@ The server's directory access control follows this flow: - Tool calls that arrive before the initial roots are loaded wait for them, so they are checked against the client's roots - **On runtime updates**: Client can send `notifications/roots/list_changed` - Server requests updated roots and replaces allowed directories again + - Once the client's roots are in force, an update with no valid roots revokes access: every path is refused ("Access denied - no allowed directories") until the client exposes a root again. Before any root has been in force, such an update keeps the command-line directories 4. **Fallback Behavior** (if client doesn't support roots) - Server continues using command-line directories only diff --git a/src/filesystem/__tests__/server-paths.test.ts b/src/filesystem/__tests__/server-paths.test.ts index 869991de8b..e382aaacf6 100644 --- a/src/filesystem/__tests__/server-paths.test.ts +++ b/src/filesystem/__tests__/server-paths.test.ts @@ -164,7 +164,7 @@ describe("relative paths", () => { path: "anything.txt", }); expect(textOf(result)).toBe( - `Access denied - path outside allowed directories: ${path.resolve(process.cwd(), "anything.txt")} not in `, + `Access denied - no allowed directories: the client's roots expose none (${path.resolve(process.cwd(), "anything.txt")})`, ); } finally { await empty.close(); diff --git a/src/filesystem/__tests__/server-roots.test.ts b/src/filesystem/__tests__/server-roots.test.ts index a35d81788c..124732dcea 100644 --- a/src/filesystem/__tests__/server-roots.test.ts +++ b/src/filesystem/__tests__/server-roots.test.ts @@ -251,6 +251,100 @@ describe("roots/list_changed", () => { ); }); + // #5094: once the client's roots are in force, an update that leaves no + // valid root revokes access instead of keeping the previous directories. + it("revokes access when the client withdraws its last root (#5094)", async () => { + let roots = [rootOf(rootDir)]; + const { client } = await connectTracked([cliDir], { + capabilities: ROOTS, + listRoots: () => roots, + }); + await vi.waitFor(async () => + expect(await allowedDirectoriesOf(client)).toEqual([rootDir]), + ); + + roots = []; + await client.sendRootsListChanged(); + await vi.waitFor(async () => + expect(await allowedDirectoriesOf(client)).toEqual([]), + ); + expect(stderr).toHaveBeenCalledWith( + "No valid root directories provided by client; access revoked until it exposes a root again", + ); + const result = await call(client, "list_directory", { path: rootDir }); + expect(result.isError).toBe(true); + expect(textOf(result)).toMatch( + /^Access denied - no allowed directories: the client's roots expose none/, + ); + // The command-line directories do not come back either. + expect( + textOf(await call(client, "list_directory", { path: cliDir })), + ).toMatch(/^Access denied/); + }); + + it("revokes access when every remaining root is invalid (#5094)", async () => { + let roots = [rootOf(rootDir)]; + const { client } = await connectTracked([], { + capabilities: ROOTS, + listRoots: () => roots, + }); + await vi.waitFor(async () => + expect(await allowedDirectoriesOf(client)).toEqual([rootDir]), + ); + + roots = [rootOf(path.join(rootDir, "gone"))]; + await client.sendRootsListChanged(); + await vi.waitFor(async () => + expect(await allowedDirectoriesOf(client)).toEqual([]), + ); + }); + + it("restores access when the client exposes a root again (#5094)", async () => { + let roots = [rootOf(rootDir)]; + const { client } = await connectTracked([], { + capabilities: ROOTS, + listRoots: () => roots, + }); + await vi.waitFor(async () => + expect(await allowedDirectoriesOf(client)).toEqual([rootDir]), + ); + roots = []; + await client.sendRootsListChanged(); + await vi.waitFor(async () => + expect(await allowedDirectoriesOf(client)).toEqual([]), + ); + + roots = [rootOf(rootDir)]; + await client.sendRootsListChanged(); + await vi.waitFor(async () => + expect(await allowedDirectoriesOf(client)).toEqual([rootDir]), + ); + }); + + it("keeps the command-line directories on an empty update before any root was in force", async () => { + let roots: Root[] = []; + const { client } = await connectTracked([cliDir], { + capabilities: ROOTS, + listRoots: () => roots, + }); + await vi.waitFor(() => + expect(stderr).toHaveBeenCalledWith( + "No valid root directories provided by client", + ), + ); + + roots = []; + await client.sendRootsListChanged(); + await vi.waitFor(() => + expect( + stderr.mock.calls.filter( + (c) => c[0] === "No valid root directories provided by client", + ), + ).toHaveLength(2), + ); + expect(await allowedDirectoriesOf(client)).toEqual([cliDir]); + }); + it("logs and keeps the current directories when the re-fetch fails", async () => { let fail = false; const { client } = await connectTracked([], { diff --git a/src/filesystem/lib.ts b/src/filesystem/lib.ts index e50a67624a..4eb1390ea1 100644 --- a/src/filesystem/lib.ts +++ b/src/filesystem/lib.ts @@ -233,7 +233,9 @@ export async function validatePath( ); if (!isAllowed) { throw new Error( - `Access denied - path outside allowed directories: ${absolute} not in ${allowedDirectories.join(", ")}`, + allowedDirectories.length === 0 + ? `Access denied - no allowed directories: the client's roots expose none (${absolute})` + : `Access denied - path outside allowed directories: ${absolute} not in ${allowedDirectories.join(", ")}`, ); } diff --git a/src/filesystem/server.ts b/src/filesystem/server.ts index a5aadf2e07..dbb955da5f 100644 --- a/src/filesystem/server.ts +++ b/src/filesystem/server.ts @@ -169,6 +169,11 @@ export function createServer( options: ServerOptions = {}, ): McpServer { let allowedDirectories = [...initialAllowedDirectories]; + // True once the client's roots have replaced the command-line directories. + // From then on a roots update replaces them even with none, so a client that + // withdraws its roots withdraws the server's access too (#5094). Until then an + // update with no valid roots keeps the command-line directories. + let rootsInForce = false; const server = new McpServer({ name: "secure-filesystem-server", @@ -760,9 +765,15 @@ export function createServer( const validatedRootDirs = await getValidRootDirectories(requestedRoots); if (validatedRootDirs.length > 0) { allowedDirectories = [...validatedRootDirs]; + rootsInForce = true; console.error( `Updated allowed directories from MCP roots: ${validatedRootDirs.length} valid directories`, ); + } else if (rootsInForce) { + allowedDirectories = []; + console.error( + "No valid root directories provided by client; access revoked until it exposes a root again", + ); } else { console.error("No valid root directories provided by client"); } From de49bdd27bb85ad889ed43d3b67aafb5deaca4d6 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 03:02:18 -0400 Subject: [PATCH 52/65] fix(filesystem): say the client exposes no valid roots, not none An update whose roots are all invalid still lists roots; the empty-allow-list message now says none of them is valid (Copilot on #5095). Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/filesystem/__tests__/server-paths.test.ts | 2 +- src/filesystem/__tests__/server-roots.test.ts | 2 +- src/filesystem/lib.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/filesystem/__tests__/server-paths.test.ts b/src/filesystem/__tests__/server-paths.test.ts index e382aaacf6..d6a8995317 100644 --- a/src/filesystem/__tests__/server-paths.test.ts +++ b/src/filesystem/__tests__/server-paths.test.ts @@ -164,7 +164,7 @@ describe("relative paths", () => { path: "anything.txt", }); expect(textOf(result)).toBe( - `Access denied - no allowed directories: the client's roots expose none (${path.resolve(process.cwd(), "anything.txt")})`, + `Access denied - no allowed directories: the client exposes no valid roots (${path.resolve(process.cwd(), "anything.txt")})`, ); } finally { await empty.close(); diff --git a/src/filesystem/__tests__/server-roots.test.ts b/src/filesystem/__tests__/server-roots.test.ts index 124732dcea..7c8aea63d9 100644 --- a/src/filesystem/__tests__/server-roots.test.ts +++ b/src/filesystem/__tests__/server-roots.test.ts @@ -274,7 +274,7 @@ describe("roots/list_changed", () => { const result = await call(client, "list_directory", { path: rootDir }); expect(result.isError).toBe(true); expect(textOf(result)).toMatch( - /^Access denied - no allowed directories: the client's roots expose none/, + /^Access denied - no allowed directories: the client exposes no valid roots/, ); // The command-line directories do not come back either. expect( diff --git a/src/filesystem/lib.ts b/src/filesystem/lib.ts index 4eb1390ea1..abb6b09cd4 100644 --- a/src/filesystem/lib.ts +++ b/src/filesystem/lib.ts @@ -234,7 +234,7 @@ export async function validatePath( if (!isAllowed) { throw new Error( allowedDirectories.length === 0 - ? `Access denied - no allowed directories: the client's roots expose none (${absolute})` + ? `Access denied - no allowed directories: the client exposes no valid roots (${absolute})` : `Access denied - path outside allowed directories: ${absolute} not in ${allowedDirectories.join(", ")}`, ); } From 423fe6b7fca01b186443b5e21bc7440fe431bb64 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 03:10:27 -0400 Subject: [PATCH 53/65] fix(filesystem): apply only the newest roots refresh's answer Each roots refresh (the initial load and every roots/list_changed) takes a generation before it asks for roots; its answer is applied only if no newer refresh has started, checked after both roots/list and validation. A stale answer is logged and discarded, so it cannot undo a newer update such as a revocation. Closes #5097 Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .changeset/filesystem-roots-refresh-order.md | 5 ++ src/filesystem/__tests__/server-roots.test.ts | 72 +++++++++++++++++++ src/filesystem/server.ts | 26 +++++-- 3 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 .changeset/filesystem-roots-refresh-order.md diff --git a/.changeset/filesystem-roots-refresh-order.md b/.changeset/filesystem-roots-refresh-order.md new file mode 100644 index 0000000000..f1cf2f7c42 --- /dev/null +++ b/.changeset/filesystem-roots-refresh-order.md @@ -0,0 +1,5 @@ +--- +"@modelcontextprotocol/server-filesystem": patch +--- + +Overlapping roots refreshes now take effect in the order they started: when a client sends `roots/list_changed` again before the previous `roots/list` answer arrived (or while the initial roots are still loading), an answer that arrives after a newer refresh has started is discarded instead of overwriting it, so a stale answer can no longer undo a newer update such as a revocation (#5097). diff --git a/src/filesystem/__tests__/server-roots.test.ts b/src/filesystem/__tests__/server-roots.test.ts index 7c8aea63d9..8e100551dd 100644 --- a/src/filesystem/__tests__/server-roots.test.ts +++ b/src/filesystem/__tests__/server-roots.test.ts @@ -345,6 +345,78 @@ describe("roots/list_changed", () => { expect(await allowedDirectoriesOf(client)).toEqual([cliDir]); }); + // #5097: the answers to overlapping refreshes can arrive out of order; only + // the newest refresh's answer is applied, so a stale one cannot undo it. + it("applies the newest of two overlapping updates when their answers arrive out of order (#5097)", async () => { + let calls = 0; + let releaseStale!: () => void; + const staleHeld = new Promise((resolve) => (releaseStale = resolve)); + const { client } = await connectTracked([], { + capabilities: ROOTS, + listRoots: async () => { + calls += 1; + if (calls === 1) return [rootOf(rootDir)]; // the initial load + if (calls === 2) { + await staleHeld; // the older update, answered last + return [rootOf(rootDir)]; + } + return []; // the newer update withdraws the root + }, + }); + await vi.waitFor(async () => + expect(await allowedDirectoriesOf(client)).toEqual([rootDir]), + ); + + await client.sendRootsListChanged(); + await vi.waitFor(() => expect(calls).toBe(2)); + await client.sendRootsListChanged(); + await vi.waitFor(async () => + expect(await allowedDirectoriesOf(client)).toEqual([]), + ); + + releaseStale(); + await vi.waitFor(() => + expect(stderr).toHaveBeenCalledWith( + "Discarded a stale roots/list answer: a newer roots update has started", + ), + ); + expect(await allowedDirectoriesOf(client)).toEqual([]); + }); + + it("discards the initial answer when an update overtakes it (#5097)", async () => { + let calls = 0; + let releaseInitial!: () => void; + const initialHeld = new Promise( + (resolve) => (releaseInitial = resolve), + ); + const { client } = await connectTracked([], { + capabilities: ROOTS, + listRoots: async () => { + calls += 1; + if (calls === 1) { + await initialHeld; + return [rootOf(rootDir)]; + } + return [rootOf(cliDir)]; + }, + }); + await vi.waitFor(() => expect(calls).toBe(1)); + await client.sendRootsListChanged(); + await vi.waitFor(() => + expect(stderr).toHaveBeenCalledWith( + "Updated allowed directories from MCP roots: 1 valid directories", + ), + ); + + releaseInitial(); + await vi.waitFor(() => + expect(stderr).toHaveBeenCalledWith( + "Discarded a stale roots/list answer: a newer roots update has started", + ), + ); + expect(await allowedDirectoriesOf(client)).toEqual([cliDir]); + }); + it("logs and keeps the current directories when the re-fetch fails", async () => { let fail = false; const { client } = await connectTracked([], { diff --git a/src/filesystem/server.ts b/src/filesystem/server.ts index dbb955da5f..0dc851819d 100644 --- a/src/filesystem/server.ts +++ b/src/filesystem/server.ts @@ -174,6 +174,12 @@ export function createServer( // withdraws its roots withdraws the server's access too (#5094). Until then an // update with no valid roots keeps the command-line directories. let rootsInForce = false; + // Each roots refresh (the initial load and every roots/list_changed) takes + // the next generation before it asks for roots. The SDK runs notification + // handlers without waiting for earlier ones, so answers can come back out of + // order; one is applied only if no newer refresh has started since, so a + // stale answer cannot undo a newer update, such as a revocation (#5097). + let rootsGeneration = 0; const server = new McpServer({ name: "secure-filesystem-server", @@ -760,9 +766,19 @@ export function createServer( }, ); - // Updates allowed directories based on MCP client roots - async function updateAllowedDirectoriesFromRoots(requestedRoots: Root[]) { + // Updates allowed directories based on MCP client roots, unless a newer + // refresh started after this one's `generation` was taken (#5097). + async function updateAllowedDirectoriesFromRoots( + requestedRoots: Root[], + generation: number, + ) { const validatedRootDirs = await getValidRootDirectories(requestedRoots); + if (generation !== rootsGeneration) { + console.error( + "Discarded a stale roots/list answer: a newer roots update has started", + ); + return; + } if (validatedRootDirs.length > 0) { allowedDirectories = [...validatedRootDirs]; rootsInForce = true; @@ -783,12 +799,13 @@ export function createServer( server.server.setNotificationHandler( "notifications/roots/list_changed", async () => { + const generation = ++rootsGeneration; try { // Request the updated roots list from the client const response = await server.server.listRoots(); /* v8 ignore else -- the SDK validates the roots/list result against ListRootsResultSchema, which requires roots, so the implicit else cannot run */ if (response && "roots" in response) { - await updateAllowedDirectoriesFromRoots(response.roots); + await updateAllowedDirectoriesFromRoots(response.roots, generation); } } catch (error) { console.error( @@ -809,11 +826,12 @@ export function createServer( const clientCapabilities = server.server.getClientCapabilities(); if (clientCapabilities?.roots) { + const generation = ++rootsGeneration; try { const response = await server.server.listRoots(); /* v8 ignore else -- the SDK validates the roots/list result against ListRootsResultSchema, which requires roots, so the else cannot run */ if (response && "roots" in response) { - await updateAllowedDirectoriesFromRoots(response.roots); + await updateAllowedDirectoriesFromRoots(response.roots, generation); } else { console.error( "Client returned no roots set, keeping current settings", From 564e84d6ae05061ad269cb26511bdd2899e73224 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 03:19:13 -0400 Subject: [PATCH 54/65] fix(filesystem): keep tool calls waiting for the update that overtook the initial roots When a roots/list_changed overtakes the initial roots/list, the initial refresh's stale answer is discarded, but it used to release the tool calls waiting on the initial load, which then ran against the command-line directories before the newer roots applied. The overtaking refresh now replaces the initialization gate while the initial load is pending, and waiting calls follow it until it settles (Copilot on #5098). Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/filesystem/__tests__/server-roots.test.ts | 77 +++++++++++++++++++ src/filesystem/server.ts | 46 +++++++---- 2 files changed, 110 insertions(+), 13 deletions(-) diff --git a/src/filesystem/__tests__/server-roots.test.ts b/src/filesystem/__tests__/server-roots.test.ts index 8e100551dd..4a36b5308a 100644 --- a/src/filesystem/__tests__/server-roots.test.ts +++ b/src/filesystem/__tests__/server-roots.test.ts @@ -417,6 +417,83 @@ describe("roots/list_changed", () => { expect(await allowedDirectoriesOf(client)).toEqual([cliDir]); }); + // #5097: a call waiting for the initial roots must not be released by the + // stale initial answer; it waits for the update that overtook it. + it("holds waiting tool calls until the update that overtook the initial load applies (#5097)", async () => { + let calls = 0; + let releaseInitial!: () => void; + let releaseUpdate!: () => void; + const initialHeld = new Promise( + (resolve) => (releaseInitial = resolve), + ); + const updateHeld = new Promise( + (resolve) => (releaseUpdate = resolve), + ); + const { client } = await connectTracked([cliDir], { + capabilities: ROOTS, + listRoots: async () => { + calls += 1; + await (calls === 1 ? initialHeld : updateHeld); + return [rootOf(rootDir)]; + }, + }); + await vi.waitFor(() => expect(calls).toBe(1)); + await client.sendRootsListChanged(); + await vi.waitFor(() => expect(calls).toBe(2)); + + releaseInitial(); + await vi.waitFor(() => + expect(stderr).toHaveBeenCalledWith( + "Discarded a stale roots/list answer: a newer roots update has started", + ), + ); + // Asked before the update has answered: it must not run against the + // command-line directory in the meantime. + let settled = false; + const pending = call(client, "list_directory", { path: cliDir }).then( + (result) => { + settled = true; + return result; + }, + ); + await new Promise((resolve) => setTimeout(resolve, 50)); + expect(settled).toBe(false); + + releaseUpdate(); + expect(textOf(await pending)).toMatch(/^Access denied/); + expect(await allowedDirectoriesOf(client)).toEqual([rootDir]); + }); + + it("releases waiting tool calls when the update that overtook the initial load fails (#5097)", async () => { + let calls = 0; + let releaseInitial!: () => void; + const initialHeld = new Promise( + (resolve) => (releaseInitial = resolve), + ); + const { client } = await connectTracked([cliDir], { + capabilities: ROOTS, + listRoots: async () => { + calls += 1; + if (calls === 1) { + await initialHeld; + return [rootOf(rootDir)]; + } + throw new Error("gone"); + }, + }); + await vi.waitFor(() => expect(calls).toBe(1)); + await client.sendRootsListChanged(); + await vi.waitFor(() => + expect(stderr).toHaveBeenCalledWith( + "Failed to request roots from client:", + "gone", + ), + ); + + releaseInitial(); + expect(await allowedDirectoriesOf(client)).toEqual([cliDir]); + }); + it("logs and keeps the current directories when the re-fetch fails", async () => { let fail = false; const { client } = await connectTracked([], { diff --git a/src/filesystem/server.ts b/src/filesystem/server.ts index 0dc851819d..7c64f4f4f5 100644 --- a/src/filesystem/server.ts +++ b/src/filesystem/server.ts @@ -193,6 +193,12 @@ export function createServer( // command-line directories (#3204). It is replaced in oninitialized, which // the SDK runs before any request sent after notifications/initialized. let initialization: Promise = Promise.resolve(); + // True from the initial roots/list until a refresh's answer has been + // applied or the newest refresh has failed. A roots/list_changed that + // overtakes the initial load while this holds replaces `initialization`, so + // waiting tool calls keep waiting for the refresh that will actually apply + // rather than running against the command-line directories (#5097). + let initialRootsPending = false; const registerTool = server.registerTool.bind(server); server.registerTool = (( name: string, @@ -200,7 +206,11 @@ export function createServer( handler: (...args: unknown[]) => unknown, ) => registerTool(name, config, (async (...args: unknown[]) => { - await initialization; + let awaited: Promise; + do { + awaited = initialization; + await awaited; + } while (awaited !== initialization); return handler(...args); }) as never)) as typeof server.registerTool; @@ -800,19 +810,26 @@ export function createServer( "notifications/roots/list_changed", async () => { const generation = ++rootsGeneration; - try { - // Request the updated roots list from the client - const response = await server.server.listRoots(); - /* v8 ignore else -- the SDK validates the roots/list result against ListRootsResultSchema, which requires roots, so the implicit else cannot run */ - if (response && "roots" in response) { - await updateAllowedDirectoriesFromRoots(response.roots, generation); + const refresh = (async () => { + try { + // Request the updated roots list from the client + const response = await server.server.listRoots(); + /* v8 ignore else -- the SDK validates the roots/list result against ListRootsResultSchema, which requires roots, so the implicit else cannot run */ + if (response && "roots" in response) { + await updateAllowedDirectoriesFromRoots(response.roots, generation); + } + } catch (error) { + console.error( + "Failed to request roots from client:", + error instanceof Error ? error.message : String(error), + ); + } finally { + if (generation === rootsGeneration) initialRootsPending = false; } - } catch (error) { - console.error( - "Failed to request roots from client:", - error instanceof Error ? error.message : String(error), - ); - } + })(); + // Overtaking the initial load: tool calls wait for this refresh instead. + if (initialRootsPending) initialization = refresh; + await refresh; }, ); @@ -827,6 +844,7 @@ export function createServer( if (clientCapabilities?.roots) { const generation = ++rootsGeneration; + initialRootsPending = true; try { const response = await server.server.listRoots(); /* v8 ignore else -- the SDK validates the roots/list result against ListRootsResultSchema, which requires roots, so the else cannot run */ @@ -842,6 +860,8 @@ export function createServer( "Failed to request initial roots from client:", error instanceof Error ? error.message : String(error), ); + } finally { + if (generation === rootsGeneration) initialRootsPending = false; } } else { if (allowedDirectories.length > 0) { From 11394a40763543e9a8c2b3c21709bdbf61dc4d28 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 03:56:52 -0400 Subject: [PATCH 55/65] test(filesystem): assert waiting calls are released before the initial answer arrives The failed-overtaking-refresh test released the held initial roots/list before asserting, so it would also pass if callers stayed blocked on it. Assert first, then release for cleanup (Copilot on #5098). Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/filesystem/__tests__/server-roots.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/filesystem/__tests__/server-roots.test.ts b/src/filesystem/__tests__/server-roots.test.ts index 4a36b5308a..30e2236d2d 100644 --- a/src/filesystem/__tests__/server-roots.test.ts +++ b/src/filesystem/__tests__/server-roots.test.ts @@ -490,8 +490,10 @@ describe("roots/list_changed", () => { ), ); - releaseInitial(); + // Asked while the initial roots/list is still unanswered: the failed update + // releases waiting calls instead of leaving them blocked on it. expect(await allowedDirectoriesOf(client)).toEqual([cliDir]); + releaseInitial(); }); it("logs and keeps the current directories when the re-fetch fails", async () => { From fc5e9c921c73dc3e007a7f960cd84e9496b304fc Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 04:06:02 -0400 Subject: [PATCH 56/65] test(filesystem): wait for the stale initial answer before teardown The failed-overtaking-refresh test returned right after releasing the held initial roots/list, so its answer handling and root validation could race afterEach's teardown. Wait for the stale answer to be discarded first (Copilot on #5099). Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/filesystem/__tests__/server-roots.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/filesystem/__tests__/server-roots.test.ts b/src/filesystem/__tests__/server-roots.test.ts index 30e2236d2d..55f1fc5f3c 100644 --- a/src/filesystem/__tests__/server-roots.test.ts +++ b/src/filesystem/__tests__/server-roots.test.ts @@ -493,7 +493,13 @@ describe("roots/list_changed", () => { // Asked while the initial roots/list is still unanswered: the failed update // releases waiting calls instead of leaving them blocked on it. expect(await allowedDirectoriesOf(client)).toEqual([cliDir]); + // Let the stale initial answer finish before teardown removes its roots. releaseInitial(); + await vi.waitFor(() => + expect(stderr).toHaveBeenCalledWith( + "Discarded a stale roots/list answer: a newer roots update has started", + ), + ); }); it("logs and keeps the current directories when the re-fetch fails", async () => { From 92f9558fbf0dbd0f1fb67d223a02c3ad5c3ad6bf Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 04:24:52 -0400 Subject: [PATCH 57/65] fix(filesystem): hold tool calls while any roots refresh is pending A runtime roots/list_changed refresh now replaces the initialization barrier, as the initial load and an overtaking refresh already did, so tool calls that arrive while its roots/list is pending wait for the result instead of running against the previous allow-list, which could still include a root the client had just withdrawn. The initialRootsPending flag is no longer needed. Closes #5101 Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .../filesystem-roots-refresh-barrier.md | 5 +++ src/filesystem/README.md | 1 + src/filesystem/__tests__/server-roots.test.ts | 39 +++++++++++++++++++ src/filesystem/server.ts | 31 ++++++--------- 4 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 .changeset/filesystem-roots-refresh-barrier.md diff --git a/.changeset/filesystem-roots-refresh-barrier.md b/.changeset/filesystem-roots-refresh-barrier.md new file mode 100644 index 0000000000..9410af03bb --- /dev/null +++ b/.changeset/filesystem-roots-refresh-barrier.md @@ -0,0 +1,5 @@ +--- +"@modelcontextprotocol/server-filesystem": patch +--- + +Tool calls that arrive while a `roots/list_changed` refresh is fetching the client's roots now wait for it, as they already did for the initial roots, so a call can no longer reach a root the client has just withdrawn while the answer is pending (#5101). diff --git a/src/filesystem/README.md b/src/filesystem/README.md index 056449ac86..bcedb5d0a9 100644 --- a/src/filesystem/README.md +++ b/src/filesystem/README.md @@ -51,6 +51,7 @@ The server's directory access control follows this flow: - Tool calls that arrive before the initial roots are loaded wait for them, so they are checked against the client's roots - **On runtime updates**: Client can send `notifications/roots/list_changed` - Server requests updated roots and replaces allowed directories again + - Tool calls that arrive while an update is being fetched wait for it, so none runs against a root the client has just withdrawn - Once the client's roots are in force, an update with no valid roots revokes access: every path is refused ("Access denied - no allowed directories") until the client exposes a root again. Before any root has been in force, such an update keeps the command-line directories 4. **Fallback Behavior** (if client doesn't support roots) diff --git a/src/filesystem/__tests__/server-roots.test.ts b/src/filesystem/__tests__/server-roots.test.ts index 55f1fc5f3c..fa97969f9a 100644 --- a/src/filesystem/__tests__/server-roots.test.ts +++ b/src/filesystem/__tests__/server-roots.test.ts @@ -502,6 +502,45 @@ describe("roots/list_changed", () => { ); }); + // #5101: a call made while a runtime refresh is pending waits for it, so it + // cannot reach a root the client has just withdrawn. + it("holds tool calls while a runtime refresh is pending (#5101)", async () => { + let calls = 0; + let releaseUpdate!: () => void; + const updateHeld = new Promise( + (resolve) => (releaseUpdate = resolve), + ); + const { client } = await connectTracked([], { + capabilities: ROOTS, + listRoots: async () => { + calls += 1; + if (calls === 1) return [rootOf(rootDir)]; + await updateHeld; // the withdrawal is answered slowly + return []; + }, + }); + await vi.waitFor(async () => + expect(await allowedDirectoriesOf(client)).toEqual([rootDir]), + ); + + await client.sendRootsListChanged(); + await vi.waitFor(() => expect(calls).toBe(2)); + let settled = false; + const pending = call(client, "list_directory", { path: rootDir }).then( + (result) => { + settled = true; + return result; + }, + ); + await new Promise((resolve) => setTimeout(resolve, 50)); + expect(settled).toBe(false); + + releaseUpdate(); + expect(textOf(await pending)).toMatch( + /^Access denied - no allowed directories/, + ); + }); + it("logs and keeps the current directories when the re-fetch fails", async () => { let fail = false; const { client } = await connectTracked([], { diff --git a/src/filesystem/server.ts b/src/filesystem/server.ts index 7c64f4f4f5..abd0815b41 100644 --- a/src/filesystem/server.ts +++ b/src/filesystem/server.ts @@ -186,19 +186,16 @@ export function createServer( version: SERVER_VERSION, }); - // Settles once the post-initialize setup (fetching the client's initial - // roots) has finished, successfully or not; it never rejects. Every tool call - // waits for it, so a call that arrives while the initial roots/list is still - // outstanding is checked against the client's roots rather than against the - // command-line directories (#3204). It is replaced in oninitialized, which - // the SDK runs before any request sent after notifications/initialized. + // Settles once the newest roots refresh has finished, successfully or not; it + // never rejects. Every tool call waits for it, so a call that arrives while + // roots are being fetched is checked against the roots that come back rather + // than the ones being replaced: the command-line directories while the + // initial roots/list is outstanding (#3204), or a root the client has just + // withdrawn while a roots/list_changed refresh is (#5101). The initial load + // (oninitialized, which the SDK runs before any request sent after + // notifications/initialized) and every refresh replace it, and a waiting + // call follows each replacement until it stops changing (#5097). let initialization: Promise = Promise.resolve(); - // True from the initial roots/list until a refresh's answer has been - // applied or the newest refresh has failed. A roots/list_changed that - // overtakes the initial load while this holds replaces `initialization`, so - // waiting tool calls keep waiting for the refresh that will actually apply - // rather than running against the command-line directories (#5097). - let initialRootsPending = false; const registerTool = server.registerTool.bind(server); server.registerTool = (( name: string, @@ -823,12 +820,11 @@ export function createServer( "Failed to request roots from client:", error instanceof Error ? error.message : String(error), ); - } finally { - if (generation === rootsGeneration) initialRootsPending = false; } })(); - // Overtaking the initial load: tool calls wait for this refresh instead. - if (initialRootsPending) initialization = refresh; + // Tool calls wait for this refresh, so none runs against a root the + // client has just withdrawn while its answer is pending (#5101). + initialization = refresh; await refresh; }, ); @@ -844,7 +840,6 @@ export function createServer( if (clientCapabilities?.roots) { const generation = ++rootsGeneration; - initialRootsPending = true; try { const response = await server.server.listRoots(); /* v8 ignore else -- the SDK validates the roots/list result against ListRootsResultSchema, which requires roots, so the else cannot run */ @@ -860,8 +855,6 @@ export function createServer( "Failed to request initial roots from client:", error instanceof Error ? error.message : String(error), ); - } finally { - if (generation === rootsGeneration) initialRootsPending = false; } } else { if (allowedDirectories.length > 0) { From dcaa6fe36081953c371cd41cb55b124219c6191d Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 04:35:46 -0400 Subject: [PATCH 58/65] fix(filesystem): install each roots refresh barrier before its roots/list is sent Each roots refresh (and the initial load) now replaces the initialization barrier before it sends roots/list, a microtask later, rather than just after. The SDK currently dispatches the client's roots/list handler asynchronously, so a newer change announced from that handler already lands after the older barrier is installed (verified: removing the deferral fails no test); the ordering is now guaranteed by the server rather than by that SDK detail (Copilot on #5103). Tests: a call queued behind a runtime refresh that fails is released against the current roots (Copilot on #5102); a refresh whose roots/list answer announces another change keeps the newest barrier (documents the case; passes with or without the deferral today). Comment wording fixed (Copilot on #5102). Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/filesystem/__tests__/server-roots.test.ts | 88 +++++++++++++++++++ src/filesystem/server.ts | 22 +++-- 2 files changed, 102 insertions(+), 8 deletions(-) diff --git a/src/filesystem/__tests__/server-roots.test.ts b/src/filesystem/__tests__/server-roots.test.ts index fa97969f9a..e42b9d356f 100644 --- a/src/filesystem/__tests__/server-roots.test.ts +++ b/src/filesystem/__tests__/server-roots.test.ts @@ -541,6 +541,94 @@ describe("roots/list_changed", () => { ); }); + it("releases a call queued behind a runtime refresh that fails (#5101)", async () => { + await fs.writeFile(path.join(rootDir, "a.txt"), "a"); + let calls = 0; + let failUpdate!: () => void; + const updateHeld = new Promise((resolve) => (failUpdate = resolve)); + const { client } = await connectTracked([], { + capabilities: ROOTS, + listRoots: async () => { + calls += 1; + if (calls === 1) return [rootOf(rootDir)]; + await updateHeld; + throw new Error("gone"); + }, + }); + await vi.waitFor(async () => + expect(await allowedDirectoriesOf(client)).toEqual([rootDir]), + ); + + await client.sendRootsListChanged(); + await vi.waitFor(() => expect(calls).toBe(2)); + // Queued while the refresh is still pending, before it fails. + let settled = false; + const pending = call(client, "list_directory", { path: rootDir }).then( + (result) => { + settled = true; + return result; + }, + ); + await new Promise((resolve) => setTimeout(resolve, 50)); + expect(settled).toBe(false); + + failUpdate(); + const result = await pending; + expect(result.isError).toBeFalsy(); + expect(textOf(result)).toBe("[FILE] a.txt"); + expect(stderr).toHaveBeenCalledWith( + "Failed to request roots from client:", + expect.stringContaining("gone"), + ); + }); + + // A client may announce a newer change while answering an older refresh's + // roots/list; the older refresh must not replace the newer one's barrier. + it("keeps the newest barrier when answering a refresh announces another change (#5101)", async () => { + let calls = 0; + let announce: () => Promise = async () => {}; + let releaseNewest!: () => void; + const newestHeld = new Promise( + (resolve) => (releaseNewest = resolve), + ); + const { client } = await connectTracked([], { + capabilities: ROOTS, + listRoots: async () => { + calls += 1; + if (calls === 1) return [rootOf(rootDir)]; + if (calls === 2) { + // Discarded on purpose: the server handles the notification on its + // own schedule, and the test waits for its roots/list instead. + void announce(); + return [rootOf(rootDir)]; + } + await newestHeld; // the newest change withdraws the root, slowly + return []; + }, + }); + announce = () => client.sendRootsListChanged(); + await vi.waitFor(async () => + expect(await allowedDirectoriesOf(client)).toEqual([rootDir]), + ); + + await client.sendRootsListChanged(); + await vi.waitFor(() => expect(calls).toBe(3)); + let settled = false; + const pending = call(client, "list_directory", { path: rootDir }).then( + (result) => { + settled = true; + return result; + }, + ); + await new Promise((resolve) => setTimeout(resolve, 50)); + expect(settled).toBe(false); + + releaseNewest(); + expect(textOf(await pending)).toMatch( + /^Access denied - no allowed directories/, + ); + }); + it("logs and keeps the current directories when the re-fetch fails", async () => { let fail = false; const { client } = await connectTracked([], { diff --git a/src/filesystem/server.ts b/src/filesystem/server.ts index abd0815b41..080be376f5 100644 --- a/src/filesystem/server.ts +++ b/src/filesystem/server.ts @@ -191,10 +191,13 @@ export function createServer( // roots are being fetched is checked against the roots that come back rather // than the ones being replaced: the command-line directories while the // initial roots/list is outstanding (#3204), or a root the client has just - // withdrawn while a roots/list_changed refresh is (#5101). The initial load - // (oninitialized, which the SDK runs before any request sent after - // notifications/initialized) and every refresh replace it, and a waiting - // call follows each replacement until it stops changing (#5097). + // withdrawn while a roots/list_changed refresh is pending (#5101). The + // initial load (oninitialized, which the SDK runs before any request sent + // after notifications/initialized) and every refresh replace it, and a + // waiting call follows each replacement until it stops changing (#5097). + // Each one is installed before its roots/list is sent: a transport that + // delivers that request synchronously can let the client announce a newer + // change at once, and that newer barrier must not be overwritten by this one. let initialization: Promise = Promise.resolve(); const registerTool = server.registerTool.bind(server); server.registerTool = (( @@ -807,7 +810,9 @@ export function createServer( "notifications/roots/list_changed", async () => { const generation = ++rootsGeneration; - const refresh = (async () => { + // Deferred a microtask, so the barrier below is installed before + // roots/list is sent (see `initialization` above). + const refresh = Promise.resolve().then(async () => { try { // Request the updated roots list from the client const response = await server.server.listRoots(); @@ -821,7 +826,7 @@ export function createServer( error instanceof Error ? error.message : String(error), ); } - })(); + }); // Tool calls wait for this refresh, so none runs against a root the // client has just withdrawn while its answer is pending (#5101). initialization = refresh; @@ -830,9 +835,10 @@ export function createServer( ); // Handles post-initialization setup, specifically checking for and fetching - // MCP roots. Tool calls wait for it (see `initialization` above). + // MCP roots. Tool calls wait for it (see `initialization` above), which is + // installed before initialize() sends its roots/list. server.server.oninitialized = () => { - initialization = initialize(); + initialization = Promise.resolve().then(initialize); }; async function initialize(): Promise { From 43980dccb757098143a11b5675a5a1262161c9b1 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 04:47:53 -0400 Subject: [PATCH 59/65] fix(filesystem): claim the initial roots load's generation before deferring it Deferring initialize() a microtask (so its barrier is installed before roots/list is sent) also deferred its generation claim. The initial load now takes its generation synchronously in oninitialized and only its request is deferred, so a roots/list_changed handled before that request runs always counts as newer (Copilot on #5102). The SDK currently runs the deferred initial load before a later notification's handler, so this ordering is not reachable today (moving the claim back fails no test); the claim makes it explicit. New test: an update announced right after initialize wins over the held initial roots. Also renames the re-entrant announcement test. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/filesystem/__tests__/server-roots.test.ts | 33 ++++++++++++++++++- src/filesystem/server.ts | 10 +++--- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/filesystem/__tests__/server-roots.test.ts b/src/filesystem/__tests__/server-roots.test.ts index e42b9d356f..4adbff48a0 100644 --- a/src/filesystem/__tests__/server-roots.test.ts +++ b/src/filesystem/__tests__/server-roots.test.ts @@ -584,7 +584,7 @@ describe("roots/list_changed", () => { // A client may announce a newer change while answering an older refresh's // roots/list; the older refresh must not replace the newer one's barrier. - it("keeps the newest barrier when answering a refresh announces another change (#5101)", async () => { + it("keeps the newest barrier when a client's roots/list answer announces another change (#5101)", async () => { let calls = 0; let announce: () => Promise = async () => {}; let releaseNewest!: () => void; @@ -629,6 +629,37 @@ describe("roots/list_changed", () => { ); }); + // An update handled before the deferred initial roots/list runs must still + // count as newer than the initial load (#5101). + it("applies an update announced right after initialize over the initial roots (#5101)", async () => { + let calls = 0; + let releaseInitial!: () => void; + const initialHeld = new Promise( + (resolve) => (releaseInitial = resolve), + ); + const conn = connectTracked([], { + capabilities: ROOTS, + listRoots: async () => { + calls += 1; + if (calls === 1) { + await initialHeld; + return [rootOf(rootDir)]; + } + return [rootOf(cliDir)]; + }, + }); + const { client } = await conn; + await client.sendRootsListChanged(); + await vi.waitFor(() => expect(calls).toBe(2)); + releaseInitial(); + await vi.waitFor(() => + expect(stderr).toHaveBeenCalledWith( + "Discarded a stale roots/list answer: a newer roots update has started", + ), + ); + expect(await allowedDirectoriesOf(client)).toEqual([cliDir]); + }); + it("logs and keeps the current directories when the re-fetch fails", async () => { let fail = false; const { client } = await connectTracked([], { diff --git a/src/filesystem/server.ts b/src/filesystem/server.ts index 080be376f5..fb472af35c 100644 --- a/src/filesystem/server.ts +++ b/src/filesystem/server.ts @@ -836,16 +836,18 @@ export function createServer( // Handles post-initialization setup, specifically checking for and fetching // MCP roots. Tool calls wait for it (see `initialization` above), which is - // installed before initialize() sends its roots/list. + // installed before initialize() sends its roots/list. The initial load claims + // its generation here, synchronously, so a roots/list_changed handled before + // the deferred request runs takes a newer one and is not discarded as stale. server.server.oninitialized = () => { - initialization = Promise.resolve().then(initialize); + const generation = ++rootsGeneration; + initialization = Promise.resolve().then(() => initialize(generation)); }; - async function initialize(): Promise { + async function initialize(generation: number): Promise { const clientCapabilities = server.server.getClientCapabilities(); if (clientCapabilities?.roots) { - const generation = ++rootsGeneration; try { const response = await server.server.listRoots(); /* v8 ignore else -- the SDK validates the roots/list result against ListRootsResultSchema, which requires roots, so the else cannot run */ From 3e41913b7bce72640b846254cf2129528ad5ace2 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 04:56:42 -0400 Subject: [PATCH 60/65] fix(filesystem): wake calls waiting on a replaced roots refresh Tool calls waited on the refresh promise itself, so a call that started waiting on refresh A stayed blocked on A's roots/list even after a newer refresh B had installed itself and applied the client's current roots; a slow or unanswered A stalled calls until its request timed out. The barrier is now separate from the refresh: each refresh installs a new barrier before it sends roots/list and settles it when it finishes, and installing one settles the barrier it replaces, so waiting calls wake and move on to the newest. This also makes the microtask deferral from the previous commits unnecessary (the barrier is installed synchronously). New test: a call waiting on an unanswered older refresh completes once a newer one finishes; it times out with the previous barrier (Copilot on #5103). Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/filesystem/__tests__/server-roots.test.ts | 42 +++++++++ src/filesystem/server.ts | 87 ++++++++++--------- 2 files changed, 87 insertions(+), 42 deletions(-) diff --git a/src/filesystem/__tests__/server-roots.test.ts b/src/filesystem/__tests__/server-roots.test.ts index 4adbff48a0..30cddff36a 100644 --- a/src/filesystem/__tests__/server-roots.test.ts +++ b/src/filesystem/__tests__/server-roots.test.ts @@ -660,6 +660,48 @@ describe("roots/list_changed", () => { expect(await allowedDirectoriesOf(client)).toEqual([cliDir]); }); + // A call waiting on an older refresh must not stay blocked on it once a newer + // refresh has applied the client's current roots (#5101). + it("wakes a call waiting on an older refresh once a newer one finishes (#5101)", async () => { + await fs.writeFile(path.join(rootDir, "a.txt"), "a"); + let calls = 0; + let releaseOlder!: () => void; + const olderHeld = new Promise((resolve) => (releaseOlder = resolve)); + const { client } = await connectTracked([], { + capabilities: ROOTS, + listRoots: async () => { + calls += 1; + if (calls === 1) return [rootOf(rootDir)]; + if (calls === 2) { + await olderHeld; // not answered until the end of the test + return []; + } + return [rootOf(rootDir)]; + }, + }); + await vi.waitFor(async () => + expect(await allowedDirectoriesOf(client)).toEqual([rootDir]), + ); + + await client.sendRootsListChanged(); + await vi.waitFor(() => expect(calls).toBe(2)); + const pending = call(client, "list_directory", { path: rootDir }); + await new Promise((resolve) => setTimeout(resolve, 50)); + await client.sendRootsListChanged(); + + expect(textOf(await pending)).toBe("[FILE] a.txt"); + expect(calls).toBe(3); + + // Cleanup: the older answer arrives last and is discarded as stale. + releaseOlder(); + await vi.waitFor(() => + expect(stderr).toHaveBeenCalledWith( + "Discarded a stale roots/list answer: a newer roots update has started", + ), + ); + expect(await allowedDirectoriesOf(client)).toEqual([rootDir]); + }); + it("logs and keeps the current directories when the re-fetch fails", async () => { let fail = false; const { client } = await connectTracked([], { diff --git a/src/filesystem/server.ts b/src/filesystem/server.ts index fb472af35c..bd0fae38b2 100644 --- a/src/filesystem/server.ts +++ b/src/filesystem/server.ts @@ -186,19 +186,25 @@ export function createServer( version: SERVER_VERSION, }); - // Settles once the newest roots refresh has finished, successfully or not; it - // never rejects. Every tool call waits for it, so a call that arrives while - // roots are being fetched is checked against the roots that come back rather - // than the ones being replaced: the command-line directories while the - // initial roots/list is outstanding (#3204), or a root the client has just - // withdrawn while a roots/list_changed refresh is pending (#5101). The - // initial load (oninitialized, which the SDK runs before any request sent - // after notifications/initialized) and every refresh replace it, and a - // waiting call follows each replacement until it stops changing (#5097). - // Each one is installed before its roots/list is sent: a transport that - // delivers that request synchronously can let the client announce a newer - // change at once, and that newer barrier must not be overwritten by this one. - let initialization: Promise = Promise.resolve(); + // The barrier every tool call waits on, so a call that arrives while roots are + // being fetched is checked against the roots that come back rather than the + // ones being replaced: the command-line directories while the initial + // roots/list is outstanding (#3204), or a root the client has just withdrawn + // while a roots/list_changed refresh is pending (#5101). Each refresh (the + // initial load included) installs a new barrier before it sends roots/list, + // and settles it when it finishes, successfully or not. Installing one also + // settles the barrier it replaces, so calls waiting on an older refresh wake + // at once and move on to the newer one instead of waiting for the older + // one's answer (#5097, #5101). + let barrier = { settled: Promise.resolve(), settle: () => {} }; + function installBarrier(): () => void { + let settle!: () => void; + const settled = new Promise((resolve) => (settle = resolve)); + const replaced = barrier; + barrier = { settled, settle }; + replaced.settle(); + return settle; + } const registerTool = server.registerTool.bind(server); server.registerTool = (( name: string, @@ -206,11 +212,11 @@ export function createServer( handler: (...args: unknown[]) => unknown, ) => registerTool(name, config, (async (...args: unknown[]) => { - let awaited: Promise; + let awaited: typeof barrier; do { - awaited = initialization; - await awaited; - } while (awaited !== initialization); + awaited = barrier; + await awaited.settled; + } while (awaited !== barrier); return handler(...args); }) as never)) as typeof server.registerTool; @@ -810,38 +816,35 @@ export function createServer( "notifications/roots/list_changed", async () => { const generation = ++rootsGeneration; - // Deferred a microtask, so the barrier below is installed before - // roots/list is sent (see `initialization` above). - const refresh = Promise.resolve().then(async () => { - try { - // Request the updated roots list from the client - const response = await server.server.listRoots(); - /* v8 ignore else -- the SDK validates the roots/list result against ListRootsResultSchema, which requires roots, so the implicit else cannot run */ - if (response && "roots" in response) { - await updateAllowedDirectoriesFromRoots(response.roots, generation); - } - } catch (error) { - console.error( - "Failed to request roots from client:", - error instanceof Error ? error.message : String(error), - ); + const settle = installBarrier(); + try { + // Request the updated roots list from the client + const response = await server.server.listRoots(); + /* v8 ignore else -- the SDK validates the roots/list result against ListRootsResultSchema, which requires roots, so the implicit else cannot run */ + if (response && "roots" in response) { + await updateAllowedDirectoriesFromRoots(response.roots, generation); } - }); - // Tool calls wait for this refresh, so none runs against a root the - // client has just withdrawn while its answer is pending (#5101). - initialization = refresh; - await refresh; + } catch (error) { + console.error( + "Failed to request roots from client:", + error instanceof Error ? error.message : String(error), + ); + } finally { + settle(); + } }, ); // Handles post-initialization setup, specifically checking for and fetching - // MCP roots. Tool calls wait for it (see `initialization` above), which is - // installed before initialize() sends its roots/list. The initial load claims - // its generation here, synchronously, so a roots/list_changed handled before - // the deferred request runs takes a newer one and is not discarded as stale. + // MCP roots. The initial load claims its generation and installs its barrier + // here, synchronously, before any roots/list_changed handled afterwards can + // claim a newer one. server.server.oninitialized = () => { const generation = ++rootsGeneration; - initialization = Promise.resolve().then(() => initialize(generation)); + const settle = installBarrier(); + // Not awaited: oninitialized is synchronous. Tool calls wait on the + // barrier, which settles when initialize() finishes; it never rejects. + void initialize(generation).finally(settle); }; async function initialize(generation: number): Promise { From b4d3a6fb5729c9040a597ed3f9cc31a96f72153d Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 12:53:27 -0400 Subject: [PATCH 61/65] ci(release): publish with Node 24's bundled npm instead of installing one OIDC trusted publishing needs npm >= 11.5.1, and Node 22 bundles npm 10.x, so publish-npm installed npm@11.20.0 next to id-token: write. It now runs an exactly pinned Node 24.21.0, whose bundled npm (11.19.0) meets the floor, and installs nothing; a step fails the job before publishing if the bundled npm is ever below 11.5.1. The build jobs keep Node 22. The release skill's note on pinning moves from the npm CLI to the Node release. Closes #5104 Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .claude/skills/release/SKILL.md | 8 ++++++-- .github/workflows/release.yml | 31 +++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 0b07c4b407..c567f3429d 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -568,5 +568,9 @@ ACTION=actions/checkout TAG=v6.1.0 echo "uses: $ACTION@$(gh api "repos/$ACTION/commits/$TAG" --jq .sha) # $TAG" ``` -The same goes for the npm CLI the publish job installs (`npm@x.y.z` in -`release.yml`): it is pinned exactly, and bumped deliberately. +The same goes for the Node release the npm publish job runs (`node-version` in +`publish-npm`, `release.yml`): it is pinned exactly, because its bundled npm is +the CLI that publishes, and it stays on a release whose bundled npm is 11.5.1 +or later (the OIDC floor; the job checks it before publishing). Bump it +deliberately, from a Node release whose npm version you have read in +`https://nodejs.org/dist/index.json`. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0729df0753..f47a698260 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -213,18 +213,33 @@ jobs: steps: # No `cache: npm`: there is no checkout, so no lockfile to key it on, and # nothing here installs dependencies to cache. + # OIDC trusted publishing requires npm >= 11.5.1. Node 22 bundles npm + # 10.x, so this job runs Node 24, whose bundled npm meets the floor, and + # installs nothing: no package is fetched from the registry and run + # next to `id-token: write` (#5104). Pinned EXACTLY, not to a major: the + # runtime is the one thing this job trusts besides the pinned actions, + # and a range would let whatever 24.x is current at release time in. + # The build jobs keep Node 22; this job only reads a manifest and runs + # `npm publish` on the verified tarball. - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: - node-version: 22 + node-version: 24.21.0 registry-url: "https://registry.npmjs.org" - # OIDC trusted publishing requires npm >= 11.5.1; Node 22's bundled npm - # is 10.x. Pinned EXACTLY, not to a range: this install runs in the job - # that holds `id-token: write`, so it is the one package this job - # trusts, and a range would let whatever 11.x is current at release time - # into it. `--ignore-scripts` keeps even its lifecycle scripts out. - - name: Install the pinned npm CLI (OIDC trusted publishing) - run: npm install -g --ignore-scripts npm@11.20.0 + # Fails closed before anything is published if the bundled npm is ever + # below the OIDC floor (a different Node pin, or a runner override). + - name: Check the bundled npm supports OIDC trusted publishing + run: | + NPM_VERSION="$(npm --version)" + echo "node $(node --version), npm $NPM_VERSION" + node -e ' + const [a, b, c] = process.argv[1].split(".").map(Number); + const ok = a > 11 || (a === 11 && (b > 5 || (b === 5 && c >= 1))); + if (!ok) { + console.error("npm " + process.argv[1] + " is below 11.5.1, the floor for OIDC trusted publishing"); + process.exit(1); + } + ' "$NPM_VERSION" - name: Download the artifact built by the build job uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 From 4d13264593602076f9e75df83d767d3e1c858c03 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 13:02:38 -0400 Subject: [PATCH 62/65] test(release): pin the publish job's npm floor check and its no-install shape scripts/release-npm-floor.test.mjs reads publish-npm out of release.yml, as release-dist-tag.test.mjs does, and runs the npm floor step with a stub npm on PATH: 11.5.1, 11.6.0, 11.19.0 and 12.0.0 pass, 11.5.0, 11.4.9, 10.9.9 and 9.0.0 fail. It also asserts an exact Node pin whose bundled npm meets the floor, the check ahead of the download and the publish, and no step that installs or runs a fetched package (comment lines ignored). The previous workflow fails all five; loosening the floor to 11.5.0 fails the refusal test (Copilot on #5105 and #5106). Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- scripts/release-npm-floor.test.mjs | 162 +++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 scripts/release-npm-floor.test.mjs diff --git a/scripts/release-npm-floor.test.mjs b/scripts/release-npm-floor.test.mjs new file mode 100644 index 0000000000..8247413e5e --- /dev/null +++ b/scripts/release-npm-floor.test.mjs @@ -0,0 +1,162 @@ +// Regression tests for how `release.yml`'s `publish-npm` job gets its npm (#5104). +// +// That job holds the OIDC credential, so it installs nothing: it runs an +// exactly pinned Node whose bundled npm meets the floor for OIDC trusted +// publishing (11.5.1), and a step fails the job before anything is published +// if that npm is ever below the floor. The step's comparator is inline shell +// and JavaScript in the workflow file, which runs only after a Release is +// published, so a later edit that let 11.5.0 through would otherwise surface +// at release time. +// +// As in release-dist-tag.test.mjs, these tests read the step out of the +// workflow file and run THAT, with `npm` on PATH replaced by a stub that +// reports a chosen version. They also pin the job's shape: an exact Node +// pin, the check ahead of the download and the publish, and no step that +// installs a package. +// +// The step's shell is bash, so those tests are skipped where there is none. +// Run via `npm run test:scripts`. + +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { + chmodSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { parse } from "yaml"; + +const repoRoot = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + "..", +); + +const CHECK_STEP = "Check the bundled npm supports OIDC trusted publishing"; + +/** The `publish-npm` job's steps, as the workflow file declares them. */ +function publishSteps() { + const workflow = parse( + readFileSync( + path.join(repoRoot, ".github", "workflows", "release.yml"), + "utf8", + ), + ); + const job = workflow.jobs["publish-npm"]; + assert.ok(job, "release.yml has no publish-npm job"); + return job.steps; +} + +const hasBash = + process.platform !== "win32" && + spawnSync("bash", ["-c", "exit 0"]).status === 0; + +/** + * Run the workflow's npm floor check with `npm --version` reporting `version`. + * + * @param {string} version what the stub npm reports + * @returns {{ status: number | null, stderr: string }} + */ +function runCheck(version) { + const step = publishSteps().find((s) => s.name === CHECK_STEP); + assert.ok(step, `publish-npm has no \`${CHECK_STEP}\` step`); + const dir = mkdtempSync(path.join(tmpdir(), "npm-floor-")); + try { + const stub = path.join(dir, "npm"); + writeFileSync(stub, `#!/bin/sh\necho "${version}"\n`); + chmodSync(stub, 0o755); + const result = spawnSync("bash", ["-e", "-c", step.run], { + encoding: "utf8", + env: { + ...process.env, + PATH: `${dir}${path.delimiter}${process.env.PATH}`, + }, + }); + return { status: result.status, stderr: result.stderr }; + } finally { + rmSync(dir, { recursive: true, force: true }); + } +} + +test( + "the npm floor check accepts 11.5.1 and later", + { skip: !hasBash && "needs bash" }, + () => { + for (const version of ["11.5.1", "11.6.0", "11.19.0", "12.0.0"]) { + const { status, stderr } = runCheck(version); + assert.equal(status, 0, `npm ${version} was refused: ${stderr}`); + } + }, +); + +test( + "the npm floor check refuses anything below 11.5.1", + { skip: !hasBash && "needs bash" }, + () => { + for (const version of ["11.5.0", "11.4.9", "10.9.9", "9.0.0"]) { + const { status, stderr } = runCheck(version); + assert.notEqual(status, 0, `npm ${version} was accepted`); + assert.match(stderr, /below 11\.5\.1/); + } + }, +); + +test("publish-npm pins Node exactly, to a release whose npm meets the floor", () => { + const setup = publishSteps().find((s) => + String(s.uses ?? "").startsWith("actions/setup-node@"), + ); + assert.ok(setup, "publish-npm has no actions/setup-node step"); + const pin = String(setup.with?.["node-version"] ?? ""); + assert.match( + pin, + /^\d+\.\d+\.\d+$/, + `node-version ${pin} is not an exact x.y.z pin`, + ); + // Node 24.5.0 is the first release whose bundled npm is 11.5.1. + const [major, minor] = pin.split(".").map(Number); + assert.ok( + major > 24 || (major === 24 && minor >= 5), + `Node ${pin} bundles an npm below the OIDC floor`, + ); +}); + +test("publish-npm checks npm before it downloads or publishes anything", () => { + const names = publishSteps().map((s) => s.name ?? s.uses); + const check = names.indexOf(CHECK_STEP); + assert.notEqual(check, -1, `publish-npm has no \`${CHECK_STEP}\` step`); + const download = names.findIndex((n) => + String(n).startsWith("Download the artifact"), + ); + const publish = names.indexOf("Publish package"); + assert.ok( + download !== -1 && publish !== -1, + "publish-npm lost its download or publish step", + ); + assert.ok( + check < download && check < publish, + `the npm check runs after: ${names.join(" > ")}`, + ); +}); + +test("publish-npm installs no package next to the OIDC credential", () => { + const install = + /\b(?:npm|pnpm|yarn)\s+(?:install|i|ci|add|exec|dlx)\b|\bnpx\b|\bcorepack\b|\bpip3?\s+install\b|\buvx?\b/; + for (const step of publishSteps()) { + if (!step.run) continue; + // Shell comments may name these tools in prose; only commands count. + const commands = step.run + .split("\n") + .filter((line) => !line.trim().startsWith("#")) + .join("\n"); + assert.doesNotMatch( + commands, + install, + `publish-npm step \`${step.name}\` installs or runs a fetched package`, + ); + } +}); From 28b468edb75b9b763fda951b90a726f1235afbfa Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 13:05:44 -0400 Subject: [PATCH 63/65] test(release): pin the ./ tarball path npm publish needs npm publish reads a bare dir/file argument as GitHub owner/repo shorthand and tries to clone it over SSH; the MCP Inspector's first release through the same split publish job failed that way (modelcontextprotocol/inspector#2551). release.yml already passes ./release-artifact/*.tgz; this asserts it, so the ./ cannot be lost in an edit. Reproduced locally with Node 24.21.0's npm: the bare path exits 128 on 'git ls-remote ssh://git@github.com/release-artifact/...', the ./ path dry-run-publishes. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- scripts/release-npm-floor.test.mjs | 32 ++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/scripts/release-npm-floor.test.mjs b/scripts/release-npm-floor.test.mjs index 8247413e5e..e53822133a 100644 --- a/scripts/release-npm-floor.test.mjs +++ b/scripts/release-npm-floor.test.mjs @@ -11,8 +11,12 @@ // As in release-dist-tag.test.mjs, these tests read the step out of the // workflow file and run THAT, with `npm` on PATH replaced by a stub that // reports a chosen version. They also pin the job's shape: an exact Node -// pin, the check ahead of the download and the publish, and no step that -// installs a package. +// pin, the check ahead of the download and the publish, no step that +// installs a package, and the `./` that makes `npm publish` read the tarball +// as a local file. Without it npm reads `release-artifact/x.tgz` as GitHub +// `owner/repo` shorthand and tries to clone it over SSH: the MCP Inspector's +// first release through the same split publish job failed exactly that way +// (modelcontextprotocol/inspector#2551). // // The step's shell is bash, so those tests are skipped where there is none. // Run via `npm run test:scripts`. @@ -160,3 +164,27 @@ test("publish-npm installs no package next to the OIDC credential", () => { ); } }); + +test("publish-npm hands npm the tarball as a ./ path, not a bare dir/file", () => { + const step = publishSteps().find((s) => s.name === "Publish package"); + assert.ok(step, "publish-npm has no `Publish package` step"); + const commands = step.run + .split("\n") + .filter((line) => !line.trim().startsWith("#")) + .join("\n"); + assert.match( + commands, + /^\s*set -- \.\/release-artifact\/\*\.tgz\s*$/m, + "the publish step must resolve the tarball as ./release-artifact/*.tgz", + ); + assert.doesNotMatch( + commands, + /set -- release-artifact\//, + "a bare release-artifact/ path makes npm read the tarball as a GitHub repo", + ); + assert.match( + commands, + /npm publish "\$1"/, + "npm publish must be given the ./ path resolved above", + ); +}); From eda464643a064d1a73c9fca5cbd6d8aeec6b7dc9 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 10 Oct 2026 13:33:02 -0400 Subject: [PATCH 64/65] fix(fetch): annotate the fetch tool The fetch tool set no tool annotations, unlike every tool in the git and time servers, and AGENTS.md requires them on every tool that is added or changed. It now declares readOnlyHint: true, destructiveHint: false, idempotentHint: true and openWorldHint: true (the values proposed on #3572), so clients can apply allow-reads/gate-sends policies to it. Protocol test pins them in tools/list; README lists them. Closes #3572 Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- src/fetch/README.md | 1 + src/fetch/src/mcp_server_fetch/server.py | 9 +++++++++ src/fetch/tests/test_protocol.py | 7 +++++++ 3 files changed, 17 insertions(+) diff --git a/src/fetch/README.md b/src/fetch/README.md index 2441bd2b89..295512bbbd 100644 --- a/src/fetch/README.md +++ b/src/fetch/README.md @@ -22,6 +22,7 @@ The fetch tool will truncate the response, but by using the `start_index` argume - `max_length` (integer, optional): Maximum number of characters to return (default: 5000) - `start_index` (integer, optional): Start content from this character index (default: 0) - `raw` (boolean, optional): Get raw content without markdown conversion (default: false) + - Annotations: `readOnlyHint: true`, `destructiveHint: false`, `idempotentHint: true`, `openWorldHint: true` (it only reads, and it reaches arbitrary hosts on the internet) ### Prompts diff --git a/src/fetch/src/mcp_server_fetch/server.py b/src/fetch/src/mcp_server_fetch/server.py index 7c338f839e..41824a2099 100644 --- a/src/fetch/src/mcp_server_fetch/server.py +++ b/src/fetch/src/mcp_server_fetch/server.py @@ -40,6 +40,7 @@ PromptMessage, TextContent, Tool, + ToolAnnotations, INVALID_PARAMS, INTERNAL_ERROR, ) @@ -418,6 +419,14 @@ async def serve( Although originally you did not have internet access, and were advised to refuse and tell the user this, this tool now grants you internet access. Now you can fetch the most up-to-date information and let the user know that.""", input_schema=Fetch.model_json_schema(), + # Reads only, changes nothing, and repeating a request has no further + # side effect; it reaches arbitrary hosts on the internet (#3572). + annotations=ToolAnnotations( + read_only_hint=True, + destructive_hint=False, + idempotent_hint=True, + open_world_hint=True, + ), ) async def list_tools( diff --git a/src/fetch/tests/test_protocol.py b/src/fetch/tests/test_protocol.py index 0a2734a19b..325e546301 100644 --- a/src/fetch/tests/test_protocol.py +++ b/src/fetch/tests/test_protocol.py @@ -129,6 +129,13 @@ async def test_list_tools_wire_shape(web: FakeWeb) -> None: ) assert "this tool now grants you internet access" in tool["description"] assert "outputSchema" not in tool + # #3572: the tool declares what it does, for clients' approval policies. + assert tool["annotations"] == { + "readOnlyHint": True, + "destructiveHint": False, + "idempotentHint": True, + "openWorldHint": True, + } assert tool["inputSchema"] == { "description": "Parameters for fetching a URL.", "properties": { From 5b400a6ec05bd883522665ebeae110bf1a2744ff Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sun, 11 Oct 2026 00:10:05 -0400 Subject: [PATCH 65/65] chore(changeset): restore the SDK v2 majors, drop fixes shipped in 1.0.0 The SDK v2 migration did not ship in 1.0.0, so its four major changesets come back with their original content (0c505a61^). The three filesystem roots changesets describe fixes cherry-picked into 1.0.0 (#5094, #5097, #5101) and would otherwise be listed again in 2.0.0's changelog. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .changeset/everything-sdk-v2.md | 5 +++++ .changeset/filesystem-revoke-empty-roots.md | 5 ----- .changeset/filesystem-roots-refresh-barrier.md | 5 ----- .changeset/filesystem-roots-refresh-order.md | 5 ----- .changeset/filesystem-sdk-v2.md | 5 +++++ .changeset/memory-sdk-v2.md | 5 +++++ .changeset/sequentialthinking-sdk-v2.md | 5 +++++ 7 files changed, 20 insertions(+), 15 deletions(-) create mode 100644 .changeset/everything-sdk-v2.md delete mode 100644 .changeset/filesystem-revoke-empty-roots.md delete mode 100644 .changeset/filesystem-roots-refresh-barrier.md delete mode 100644 .changeset/filesystem-roots-refresh-order.md create mode 100644 .changeset/filesystem-sdk-v2.md create mode 100644 .changeset/memory-sdk-v2.md create mode 100644 .changeset/sequentialthinking-sdk-v2.md diff --git a/.changeset/everything-sdk-v2.md b/.changeset/everything-sdk-v2.md new file mode 100644 index 0000000000..07fe4f38ae --- /dev/null +++ b/.changeset/everything-sdk-v2.md @@ -0,0 +1,5 @@ +--- +"@modelcontextprotocol/server-everything": major +--- + +Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`, `@modelcontextprotocol/node`, and `@modelcontextprotocol/server-legacy` for the deprecated HTTP+SSE transport) from `@modelcontextprotocol/sdk` 1.x (#4856). The task tools `simulate-research-query`, `trigger-sampling-request-async` and `trigger-elicitation-request-async` are removed, and the server no longer advertises the `tasks` capability, because SDK v2 dropped the experimental tasks layer they were built on; they return on the tasks extension in #4852. Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. The optional `state` argument of `args-prompt` now carries its description. diff --git a/.changeset/filesystem-revoke-empty-roots.md b/.changeset/filesystem-revoke-empty-roots.md deleted file mode 100644 index f563e3373a..0000000000 --- a/.changeset/filesystem-revoke-empty-roots.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-filesystem": patch ---- - -A `roots/list_changed` update that leaves no valid root now revokes access once the client's roots are in force, instead of keeping the previous allowed directories, so a client that withdraws its roots withdraws the server's access too (#5094). Every path is then refused with "Access denied - no allowed directories" until the client exposes a root again. Before any root has been in force, such an update still keeps the command-line directories. diff --git a/.changeset/filesystem-roots-refresh-barrier.md b/.changeset/filesystem-roots-refresh-barrier.md deleted file mode 100644 index 9410af03bb..0000000000 --- a/.changeset/filesystem-roots-refresh-barrier.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-filesystem": patch ---- - -Tool calls that arrive while a `roots/list_changed` refresh is fetching the client's roots now wait for it, as they already did for the initial roots, so a call can no longer reach a root the client has just withdrawn while the answer is pending (#5101). diff --git a/.changeset/filesystem-roots-refresh-order.md b/.changeset/filesystem-roots-refresh-order.md deleted file mode 100644 index f1cf2f7c42..0000000000 --- a/.changeset/filesystem-roots-refresh-order.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@modelcontextprotocol/server-filesystem": patch ---- - -Overlapping roots refreshes now take effect in the order they started: when a client sends `roots/list_changed` again before the previous `roots/list` answer arrived (or while the initial roots are still loading), an answer that arrives after a newer refresh has started is discarded instead of overwriting it, so a stale answer can no longer undo a newer update such as a revocation (#5097). diff --git a/.changeset/filesystem-sdk-v2.md b/.changeset/filesystem-sdk-v2.md new file mode 100644 index 0000000000..523a805d57 --- /dev/null +++ b/.changeset/filesystem-sdk-v2.md @@ -0,0 +1,5 @@ +--- +"@modelcontextprotocol/server-filesystem": major +--- + +Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x (#4856). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); and tools no longer advertise `execution.taskSupport`. diff --git a/.changeset/memory-sdk-v2.md b/.changeset/memory-sdk-v2.md new file mode 100644 index 0000000000..e1ab5b57df --- /dev/null +++ b/.changeset/memory-sdk-v2.md @@ -0,0 +1,5 @@ +--- +"@modelcontextprotocol/server-memory": major +--- + +Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x (#4856). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`. diff --git a/.changeset/sequentialthinking-sdk-v2.md b/.changeset/sequentialthinking-sdk-v2.md new file mode 100644 index 0000000000..9e76a69e93 --- /dev/null +++ b/.changeset/sequentialthinking-sdk-v2.md @@ -0,0 +1,5 @@ +--- +"@modelcontextprotocol/server-sequential-thinking": major +--- + +Move to the TypeScript SDK v2 packages (`@modelcontextprotocol/server`) from `@modelcontextprotocol/sdk` 1.x (#4856). Breaking for clients: the server now requires Node.js 20 or later; over stdio, a single incoming JSON-RPC message larger than 10 MiB now closes the connection (the SDK v2 default read-buffer cap; SDK 1.x buffered without a limit); an unknown tool name is rejected with JSON-RPC error `-32602` instead of returning an `isError` result; error messages no longer carry the `MCP error : ` prefix, and input-validation messages name the field first (`field: message`); tool schemas in `tools/list` declare JSON Schema 2020-12 instead of draft-07; and tools no longer advertise `execution.taskSupport`.