refactor(sdk): ship the destructive-confirmation helpers in pipefy_sdk - #651
Open
mocha06 wants to merge 1 commit into
Open
refactor(sdk): ship the destructive-confirmation helpers in pipefy_sdk#651mocha06 wants to merge 1 commit into
mocha06 wants to merge 1 commit into
Conversation
The SDK ships ungated delete_*/remove_* methods and a docstring obligation to gate them, but not the code that does the gating. The canonical implementation lived in pipefy-mcp-server, which an in-process SDK consumer cannot import without taking the mcp[cli]==2.0.* pin and a package whose identity resolution raises outside the server request scope. So each consumer reimplements the token logic and drifts from what the MCP server enforces. Move mint_confirmation_token, verify_confirmation_token and classify_confirmation_token_failure into pipefy_sdk.destructive_confirmation and export them from the package root. The three functions and every private helper are byte-identical, so the token wire format does not move; a golden vector pins it. Add confirmation_signing_key, which derives one caller's HMAC key from that caller's credential. Deriving per caller is what stops one caller's token from confirming another's deletion, and the SDK previously shipped that instruction with no helper behind it. signing_key_for in the MCP guard now calls it, so both sides share one derivation. Delete pipefy_mcp.tools.destructive_confirmation_token rather than leave a deprecated shim. pipefy_mcp.tools exports only PIPEFY_TOOL_NAMES and ToolRegistry, and docs/DEPRECATION.md scopes the MCP surface to tool names and argument shapes, so the path was never public. The module is stdlib-only and the SDK dependency list is unchanged. No tool name, schema, annotation, envelope, or preview text changes. Closes #649. Signed-off-by: mocha06 <52426811+mocha06@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #649.
Motivation
The SDK ships ungated
delete_*/remove_*methods — 56 across the package — plus a docstring obligation to gate them, but not the code that does the gating.delete_pipeanddelete_tableboth say "Caller must enforce preview/confirm UX".The canonical implementation of that protocol lived in
pipefy-mcp-server. An application that embeds the SDK in process cannot import it without taking two things it does not want. First, themcp[cli]==2.0.*pin, which conflicts with any environment holdingmcp1.x. Second, a package whose identity resolution raises outside the server request scope by design. So every in-process consumer that wants to honour the contract reimplements the token logic, and drifts from the behaviour the MCP server enforces.Outcome
mint_confirmation_token,verify_confirmation_token,classify_confirmation_token_failureandconfirmation_signing_keyare importable frompipefy_sdk. An in-process consumer runs the same protocol the MCP server runs, from the same code.pipefy_sdk.destructive_confirmation, stdlib-only. The SDK dependency list is unchanged: 8Requires-Distentries, read off the built wheel.confirmation_signing_keyis new. It derives one caller's HMAC key from that caller's credential, which is what stops one caller's token from confirming another's deletion.signing_key_forin the MCP guard calls it, so both sides share one derivation.pipefy_mcp.tools.destructive_confirmation_tokenis deleted. The guard imports frompipefy_sdk.Read
packages/sdk/src/pipefy_sdk/destructive_confirmation.pyfirst. Its only change against the old module is the addedconfirmation_signing_key. The three original functions and every private helper are byte-identical.Testing
Offline: full suite 4762 passed / 39 skipped,
ruff check,ruff format --check,lint-imports(2 contracts kept),bump_version.py verify, the skill-reference and Cursor-plugin linters,uv build --all-packages.The live runs below drove the
pipefy-mcp-serverbinary built from this branch, over the real MCP protocol, against a prod organization. They used throwaway pipes only. All seven were created and deleted by the runs, and each deletion was confirmed afterwards.Local profile, stdio
delete_piperegistereddelete_pipe confirm=falserequires_confirmation=true, token mintedget_pipeafter the previewconfirm=true, token omittedrequires_confirmation=truerequires_confirmation=truerequires_confirmation=trueget_pipeafter three rejected confirmationsconfirm=truewith the matching tokenPipe ... was permanently deleted.get_pipeimmediately after the deletionrequires_confirmation=trueRow 8 is a property of the Pipefy API, not of this change. Pipe deletion is eventually consistent: polling shows the pipe stops resolving 6.6 s after the mutation returns success. The deletion mutation itself is untouched SDK code.
A separate stdio run replayed one token across two server processes. Process B rejected process A's token. That confirms the local profile falls back to the per-process random key and never reaches
confirmation_signing_key.Remote profile, Streamable HTTP with a validated bearer
tools/listwith no bearerdelete_pipepresent, surface smaller than local's 187delete_pipe confirm=falserequires_confirmation=true, token mintedPipe ... was permanently deleted.requires_confirmation=trueRow 4 covers the changed line. The same cross-process replay is rejected on stdio. A token survives a server restart only when its key comes from the caller rather than from the process, so row 4 is what proves
confirmation_signing_key(bearer)runs.Not verified:
mcp.pipefy.com. The hosted deployment pins an exact published release ofpipefy-mcp-server(0.5.0b1today), so it cannot exercise this branch. Hosted coverage follows the release and the pin bump. The remote profile above therefore ran locally against the real issuer.Notes
Issue #649 asked for a deprecated re-export shim at the old path. This drops it.
pipefy_mcp.tools.__init__exports onlyPIPEFY_TOOL_NAMESandToolRegistry, anddocs/DEPRECATION.mdscopes the MCP surface to tool names and argument shapes, so the deleted path was never public.