Skip to content

chore: release v0.5.0-alpha.1 - #558

Merged
mocha06 merged 1 commit into
devfrom
rc-dev/release/v0.5.0-alpha.1
Jul 31, 2026
Merged

chore: release v0.5.0-alpha.1#558
mocha06 merged 1 commit into
devfrom
rc-dev/release/v0.5.0-alpha.1

Conversation

@mocha06

@mocha06 mocha06 commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Cuts the staging alpha v0.5.0-alpha.1, so the hosted MCP server's deployment wrapper can pin an exact version and exercise this release in staging before it ships. Tagging publishes a GitHub Release with wheels and a pre-release PyPI upload.

What this PR contains

  • Lockstep version bump 0.4.0-beta.2 -> 0.5.0-alpha.1 across every workspace package's __init__.py, the root pyproject.toml, .claude-plugin/plugin.json, and uv.lock.
  • CHANGELOG.md: not stamped. ## [Unreleased] keeps accumulating across alphas and is what the Release workflow uses as this tag's release body, so the eventual beta promotion still stamps the whole set into one section.

Released content (already in dev)

Added

  • Release process: alpha releases ship from dev, beta and stable releases from main. scripts/release.py alpha-pr <bump> branches off origin/dev, bumps the lockstep version, and opens a release PR into dev; once merged, publish from dev tags the commit and pushes the tag, whose wheels reach PyPI as a pre-release so the hosted MCP server's deployment wrapper can pin an exact version and exercise a release in staging before it ships. release-pr beta then promotes the alpha to the first beta of the same X.Y.Z (0.5.0-alpha.3 -> 0.5.0-beta.1) through the dev -> main release PR, and publish from main ships it. alpha-pr is release-pr based on dev, so both lines take the same reviewed path — a repository ruleset requires a pull request on main and dev alike, and no flow pushes a release branch: the bump always arrives via a merged PR and publish pushes only the tag, asserting the branch matches its remote so an unmerged release PR is reported as such. An alpha deliberately does not stamp CHANGELOG.md: ## [Unreleased] keeps accumulating across alpha.1..alpha.N and is what the Release workflow uses as an alpha's release body, so the beta promotion still stamps the whole set into one section. Which branch a release ships from is derived from the version's own pre-release track rather than the operator's checkout, and enforced before the bump, again before the tag, and in the Release workflow itself, which asserts the tagged commit is an ancestor of origin/dev for an alpha or origin/main otherwise before any wheel is built.

Changed (breaking, internal API)

  • MCP: ported to the mcp SDK 2.x (mcp[cli]==2.0.*), superseding both the >=1.29.0,<2 floor bump and the <2 cap entry listed below in this same cycle, whose closing note that the upper bound stays no longer describes the shipped pin. 2.0 removes the mcp.server.fastmcp module and renames FastMCP to MCPServer, so the pin could not move without the port. Nothing changes for a caller using the server over stdio or HTTP: the 187-tool surface, the --toolsets selections, the remote-safe floor, the tool-error envelope, and the structured log shapes are all unchanged, verified by the full suite plus a live smoke against the API. What changes is the code that embeds this package.
    • The tool-call middleware seam moved onto the SDK's own. The chain used to wrap the low-level server's private request_handlers[CallToolRequest] slot; 2.0 removes that dict and ships MCPServer(middleware=[...]) instead. install_tool_call_middleware becomes build_tool_call_middleware, which returns one ServerMiddleware for the composition root to pass to the constructor. extra_tool_middlewares on build_pipefy_mcp_server is unchanged, so an embedder registering middleware needs no edit; an embedder that called install_tool_call_middleware directly does. Three shape changes for a middleware author. ToolCallContext.req is gone (read tool_name and arguments, now raw pre-validation values off the params). The seam's return type is the SDK's HandlerResult (BaseModel | dict[str, Any] | None) rather than a types.ServerResult, and a middleware that short-circuits returns the serialized wire dict short_circuit_error builds, not a model. And ToolCallContext carries a required protocol_version: short_circuit_error shapes its result against it, so any code constructing a context by hand (a middleware test fixture, a synthetic call) has to supply one.
    • serverInfo.version now reports this package's version (pipefy_mcp.__version__) instead of the SDK's. FastMCP neither accepted nor forwarded a version, so the advertised string tracked mcp itself; MCPServer takes one.
    • host, transport_security, and json_response leave the server constructor for the per-transport call, so wire_hosted_observability takes them. port is unaffected: it only ever reached the uvicorn bind. An embedder that calls streamable_http_app() itself must pass json_response there, or its POST replies become SSE-framed instead of plain JSON. core/fastmcp_tool_lifecycle.py is deleted in favour of the public MCPServer.remove_tool. transport_security_for pairs the resource-server URL parse with the allowlist build in one place.
    • context_from_request is renamed context_from_server_request, and its input changes from a types.CallToolRequest model to a ServerRequestContext. An embedder importing the old name from pipefy_mcp.core.tool_middleware gets an ImportError; the arguments and request id are read off the context (ctx.params, ctx.request) rather than off req.params.
    • McpRuntime.transport_security is removed. The SDK takes the DNS-rebinding allowlist per transport, on streamable_http_app(), so it no longer travels on the runtime; reading the attribute raises AttributeError. Resolve it with transport_security_for(settings) from pipefy_mcp.core.transport_security and pass the result to the serving call, as run_server does.
    • What call_next returns inside a tool-call middleware is now polymorphic. The SDK's own handler returns the serialized wire dict, keyed isError in camelCase, and so does short_circuit_error; a CallToolResult model, with a snake_case is_error, only arrives from an inner middleware that built its result by hand. Read the flag through result_is_error(result), the supported accessor. A 1.x-style attribute read silently returns the default on the dict, so every real tool failure reads as a success. That exact read caused a regression during this port.
    • A short-circuiting middleware owns its response envelope, and short_circuit_error now shapes one. The SDK shapes a handler's result for the wire per negotiated revision inside the middleware chain, so a middleware that returns without awaiting call_next is never shaped. Returned as a CallToolResult, its 2.0 resultType default ("complete", a 2026-07-28 field) reached the wire on a connection that negotiated 2025-11-25, invisibly, because a client's surface validation ignores unknown fields. short_circuit_error now takes the ToolCallContext as its first argument and returns the wire dict, shaped by the SDK's own serialize_server_result against ctx.protocol_version. A middleware author updates the call (short_circuit_error(ctx, "quota exceeded", code="RATE_LIMITED")); nothing changes for a client. ToolCallContext gains protocol_version.
    • A tools/call that names no tool is logged under tool: "<unnamed>" rather than an empty string, so it does not share a dashboard bucket with the real tool names. The call still reaches the middleware chain, and ctx.tool_name is still "", the raw view of what the client sent.
    • Mounting the HTTP app carries a lifespan obligation. The app wire_hosted_observability returns owns the lifespan that enters session_manager.run(). Serving it directly (what run_server does, and what a uvicorn deployment does) needs nothing extra. An embedder that Mounts it under a host Starlette or FastAPI app must enter app.session_manager.run() from the host's own lifespan, because Starlette does not run a mounted app's lifespan; skipping it leaves the session manager unstarted and every request fails.
    • The 4 MiB Streamable HTTP body limit (a 413 above it) carries forward from the 1.29.0 floor bump documented below, unchanged: the wiring passes no max_request_body_size, so the SDK default stands. Attachments are unaffected (no tool takes bytes or base64); a long free-text argument is the reachable case. A hosted deployment that needs a larger body raises it on the streamable_http_app() call.
    • The pin is the 2.0 patch line, not <3. Two of the surfaces this package builds on sit outside what SemVer covers: the MCPServer(middleware=[...]) list, which upstream marks provisional, and the Tool.run patch behind the argument-validation envelope. A minor release can move either with no signal, and no CI job resolves past uv.lock, so nothing would catch it before a release. Patch releases are accepted; widening to a new minor is a deliberate change that re-tests both surfaces.
    • Transitive dependency changes worth noting for image builds: the SDK now uses httpx2 rather than httpx (this package still declares httpx itself, and nothing hands an httpx object into the SDK), and adds mcp-types, opentelemetry-api, and truststore while dropping httpx-sse. truststore validates TLS against the OS trust store, so a minimal container with no system CA store needs SSL_CERT_FILE. Two transitive majors arrive together: starlette 0.46.1 to 1.3.1 and sse-starlette 2.2.1 to 3.4.6. starlette is now a declared dependency of pipefy-mcp-server (starlette>=1.3.1,<2) rather than an undeclared transitive, because four modules import it directly (the Starlette app wire_hosted_observability returns, and the Request annotation on McpRuntime.session_for_request and the two identity resolvers), so an SDK release that dropped or vendored it would have broken those imports with no bound to catch it. uvicorn is now declared for the same reason (uvicorn>=0.34.0,<1): run_server imports it and builds the Config/Server pair on the --transport http path, while the SDK requires it only under a sys_platform != 'emscripten' marker, so the transitive guarantee was conditional.
    • The SDK deprecates the logging capability in the 2026-07-28 revision (SEP-2577), so every ctx.debug call in the tool modules emits MCPDeprecationWarning — around a thousand warning records in a suite run, against a previously clean baseline. Migrating those calls is a behavior change deserving its own review, so the suite carries one scoped filterwarnings ignore instead, matched on the message rather than the class so the SDK's other deprecation subjects still surface. The ignore is bounded by construction: when the SDK removes the capability the calls break outright and the suite fails regardless.
    • Part of Decision: stay on mcp 1.x for now, with a scoped 2.x migration plan #543. The server negotiates 2025-11-25 on the initialize handshake, but a client that opens with the enveloped server/discover reaches 2026-07-28, which a default mcp 2.x Client (mode="auto") does. Adopting that revision as the served default is still separate work; what changed here is that the tools now behave correctly on both.

Changed (installer)

  • Installer: install.sh resolves the newest GitHub Release whose tag is not an alpha, so a staging alpha published from dev never becomes what a default curl … | sh install hands out; the public install path stays on the beta line. Alphas are also flagged as GitHub pre-releases, keeping the "Latest" badge on the newest beta. Filtering is by tag shape rather than the API's prerelease flag, because the whole pre-1.0 line is betas that must stay installable. install.sh --version vX.Y.Z-alpha.N still installs an alpha on purpose.

After merge

Run uv run python scripts/release.py publish from dev to tag and publish — the one irreversible, human-gated step.

@mocha06
mocha06 merged commit 7e787b1 into dev Jul 31, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants