From e85070ac909b172b86602363989ddbff9c6aa21e Mon Sep 17 00:00:00 2001 From: Gabriel Date: Sun, 31 May 2026 21:31:04 -0300 Subject: [PATCH 1/6] feat(plugin): install via release wheel URLs Switch `.mcp.json` and `commands/install.md` from `uvx --from git+...@latest#subdirectory=...` to direct GitHub Release wheel URLs. Eliminates the full git clone + setuptools build pass that every plugin install or `--refresh` previously paid per workspace member (sdk, auth, infra, mcp). The release workflow gains a step that, after creating the GitHub Release, patches `.mcp.json`, `commands/install.md`, `.claude-plugin/plugin.json`, and `.claude-plugin/marketplace.json` on `main` to point at the just-cut tag's wheels and commits the result. README MCP-client wiring blocks (Cursor, Claude Desktop, Codex) show the same wheel-URL shape with `` and `` placeholders; the curl installer (#231) will resolve them automatically once it ships. Closes #234. --- .github/workflows/release.yml | 50 +++++++++++++++++++++++++++++++++++ .mcp.json | 8 +++--- CHANGELOG.md | 3 ++- RELEASE.md | 4 +-- commands/install.md | 9 ++++--- 5 files changed, 64 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2178f17d..3ec3448b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -87,6 +87,56 @@ jobs: with: files: dist/*.whl body_path: CHANGELOG_RELEASE_BODY.md + - name: Patch plugin manifests on main to new tag's wheel URLs + if: startsWith(github.ref_name, 'v') + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git fetch origin main + git checkout -B main origin/main + python <<'PY' + import json + import os + import re + from pathlib import Path + + tag = os.environ["GITHUB_REF_NAME"] + ver = tag.removeprefix("v") + pep = ver.replace("-beta.", "b").replace("-rc.", "rc").replace("-alpha.", "a") + repo = os.environ["GITHUB_REPOSITORY"] + base = f"https://github.com/{repo}/releases/download/{tag}" + + url_pattern = re.compile( + r"https://github\.com/[\w./-]+/releases/download/v[\w.+\-]+/(pipefy_\w+)-[\w.+]+-py3-none-any\.whl" + ) + + def rewrite(match: re.Match[str]) -> str: + pkg = match.group(1) + return f"{base}/{pkg}-{pep}-py3-none-any.whl" + + for path in ("commands/install.md", ".mcp.json"): + p = Path(path) + p.write_text(url_pattern.sub(rewrite, p.read_text(encoding="utf-8")), encoding="utf-8") + + for path, key_path in [ + (".claude-plugin/plugin.json", ("version",)), + (".claude-plugin/marketplace.json", ("plugins", 0, "version")), + ]: + p = Path(path) + data = json.loads(p.read_text(encoding="utf-8")) + obj = data + for k in key_path[:-1]: + obj = obj[k] + obj[key_path[-1]] = ver + p.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") + PY + git add .mcp.json commands/install.md .claude-plugin/plugin.json .claude-plugin/marketplace.json + if git diff --cached --quiet; then + echo "No manifest changes to commit" + exit 0 + fi + git commit -m "chore(release): point plugin manifests at ${GITHUB_REF_NAME} wheels" + git push origin main - name: Stage PyPI wheels (SDK + CLI + MCP + Auth + Infra) if: startsWith(github.ref_name, 'v1.') && !contains(github.ref_name, '-') run: | diff --git a/.mcp.json b/.mcp.json index ae20ab97..417b9fca 100644 --- a/.mcp.json +++ b/.mcp.json @@ -4,11 +4,13 @@ "command": "uvx", "args": [ "--with", - "pipefy-sdk @ git+https://github.com/gbrlcustodio/pipefy-mcp-server@latest#subdirectory=packages/sdk", + "https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download/v0.2.0-beta.2/pipefy_sdk-0.2.0b2-py3-none-any.whl", "--with", - "pipefy-auth @ git+https://github.com/gbrlcustodio/pipefy-mcp-server@latest#subdirectory=packages/auth", + "https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download/v0.2.0-beta.2/pipefy_auth-0.2.0b2-py3-none-any.whl", + "--with", + "https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download/v0.2.0-beta.2/pipefy_infra-0.2.0b2-py3-none-any.whl", "--from", - "git+https://github.com/gbrlcustodio/pipefy-mcp-server@latest#subdirectory=packages/mcp", + "https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download/v0.2.0-beta.2/pipefy_mcp_server-0.2.0b2-py3-none-any.whl", "pipefy-mcp-server" ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 34d898ca..3c869362 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - **Infra**: `security.assert_hostname_resolves_to_public_ips` raises `ValueError` instead of silently passing when `socket.getaddrinfo` returns an empty address list. `UnicodeError` from IDN encoding (e.g. labels > 63 chars) is now caught alongside `socket.gaierror` and surfaced as a `ValueError` with the same `Could not resolve hostname` shape. - **Auth / SDK**: every `PIPEFY_*` env var is validated against a semantically meaningful pattern at settings construction. URL env vars (`PIPEFY_BASE_URL`, `PIPEFY_AUTH_URL`) require `https?://` plus non-whitespace; credential fields (`PIPEFY_TOKEN`, `PIPEFY_SERVICE_ACCOUNT_CLIENT_ID`, `PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET`, `PIPEFY_AUTH_CLIENT_ID`) reject leading / trailing whitespace; `PIPEFY_ORG_ID` must be an ASCII numeric string. There is no longer an empty-string opt-out for any tier — unset the variable to fall back to the default, or use `PIPEFY_DISABLE_STORED_SESSION=1` to turn the stored-session tier off explicitly (see the **Added** entry above). - **SDK / MCP / CLI**: renamed `--graphql-url` CLI flag to `--base-url` to match the new env-var shape. -- **Install snippets**: every `git+https://github.com/gbrlcustodio/pipefy-mcp-server` URL in the root `README.md`, `packages/mcp/README.md`, `packages/cli/README.md`, the shipping `.mcp.json`, and `commands/install.md` now pins `@latest` — a moving git tag the release flow updates to point at the most recent release. Users get release-stable installs by default; `@v0.2.0-beta.1`-style pins remain available for reproducibility. The release process documented in `RELEASE.md` now includes `git tag -f latest && git push --force-with-lease origin latest` as the step that rolls new installs forward. +- **Install snippets**: every `git+https://github.com/gbrlcustodio/pipefy-mcp-server` URL in the root `README.md`, `packages/mcp/README.md`, and `packages/cli/README.md` now pins `@latest` — a moving git tag the release flow updates to point at the most recent release. Users get release-stable installs by default; `@v0.2.0-beta.1`-style pins remain available for reproducibility. The release process documented in `RELEASE.md` now includes `git tag -f latest && git push --force-with-lease origin latest` as the step that rolls new installs forward. +- **Plugin / MCP**: `.mcp.json` and `commands/install.md` resolve through pre-built wheels attached to each GitHub Release (`releases/download/v/pipefy_--py3-none-any.whl`) instead of `uvx --from git+@latest#subdirectory=...`. Drops the full git clone + setuptools build pass on every plugin install or `--refresh`. The release workflow auto-patches `.mcp.json`, `commands/install.md`, `.claude-plugin/plugin.json`, and `.claude-plugin/marketplace.json` to the just-cut tag on each `v*` push and commits the result to `main`. The README MCP-client wiring blocks (Cursor, Claude Desktop, Codex) show the same wheel-URL shape with `` / `` placeholders for manual substitution; the curl installer ([#231](https://github.com/gbrlcustodio/pipefy-mcp-server/issues/231)) will resolve them automatically. Closes #234. ### Deprecated diff --git a/RELEASE.md b/RELEASE.md index 6ddb49e8..97973364 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -36,13 +36,13 @@ The Release workflow requires the git tag (without leading `v`) to **exactly mat git tag vX.Y.Z && git push origin main && git push origin vX.Y.Z ``` -6. Roll the `latest` moving tag to point at the same commit. The README install snippets and shipping `.mcp.json` pin `@latest`, so this is what makes new installs pick up the release: +6. Roll the `latest` moving tag to point at the same commit. The README MCP-client wiring snippets and the `packages/{cli,mcp}/README.md` install commands pin `@latest`, so this is what makes new installs from those copy-paste paths pick up the release: ```bash git tag -f latest vX.Y.Z && git push --force-with-lease origin latest ``` -7. Wait for the **Release** workflow (`.github/workflows/release.yml`) to finish. +7. Wait for the **Release** workflow (`.github/workflows/release.yml`) to finish. As part of the run it auto-commits a `chore(release): point plugin manifests at vX.Y.Z wheels` patch to `main`, pointing `.mcp.json`, `commands/install.md`, `.claude-plugin/plugin.json`, and `.claude-plugin/marketplace.json` at the just-uploaded wheels. The next `dev → main` release PR will conflict on these four files; resolve by taking dev's version (it already carries the bump for the next beta) and letting the workflow re-patch on tag push. 8. Confirm the GitHub Release lists the built wheels (`pipefy_cli-*.whl`, `pipefy_mcp_server-*.whl`, `pipefy_sdk-*.whl`, `pipefy_auth-*.whl`, and `pipefy_infra-*.whl`). Optionally verify install from the tag, for example: ```bash diff --git a/commands/install.md b/commands/install.md index 6f2e63ec..99866c4d 100644 --- a/commands/install.md +++ b/commands/install.md @@ -1,6 +1,6 @@ --- name: install -description: Install the Pipefy CLI (pipefy-cli + pipefy-auth + pipefy-sdk) as a persistent uv tool. +description: Install the Pipefy CLI (pipefy-cli + pipefy-auth + pipefy-infra + pipefy-sdk) as a persistent uv tool. disable-model-invocation: true --- @@ -10,9 +10,10 @@ Otherwise prompt the user to confirm running: ``` uv tool install --force \ - --with "pipefy-sdk @ git+https://github.com/gbrlcustodio/pipefy-mcp-server@latest#subdirectory=packages/sdk" \ - --with "pipefy-auth @ git+https://github.com/gbrlcustodio/pipefy-mcp-server@latest#subdirectory=packages/auth" \ - "git+https://github.com/gbrlcustodio/pipefy-mcp-server@latest#subdirectory=packages/cli" + --with https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download/v0.2.0-beta.2/pipefy_sdk-0.2.0b2-py3-none-any.whl \ + --with https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download/v0.2.0-beta.2/pipefy_auth-0.2.0b2-py3-none-any.whl \ + --with https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download/v0.2.0-beta.2/pipefy_infra-0.2.0b2-py3-none-any.whl \ + https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download/v0.2.0-beta.2/pipefy_cli-0.2.0b2-py3-none-any.whl ``` Verify with `pipefy --version`. From bef8f75aeee386346bf33f7f53c89273e72a37d2 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Mon, 1 Jun 2026 10:10:51 -0300 Subject: [PATCH 2/6] chore(release): tidy patch step (drop redundant guard, restore tag tree) - Drop the `if: startsWith(github.ref_name, 'v')` guard on the new manifest-patch step. The workflow trigger is already `tags: ["v*"]`, so the conditional was always true. - Restructure the empty-diff check as `if !; else` so the no-op path doesn't `exit 0` early. The new `git checkout ${GITHUB_SHA}` line needs to run on both branches. - After pushing to main, `git checkout ${GITHUB_SHA}` so the working tree is back at the tag commit. Subsequent steps (Stage PyPI / Smoke / Upload) use untracked dist/ paths and are unaffected today, but a future step that reads tracked files would otherwise silently see main's tip instead of the tag. - Document the PEP 440 normalization's scope inline. - Alphabetize the package list in `commands/install.md`'s description frontmatter. --- .github/workflows/release.yml | 14 +++++++++----- commands/install.md | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3ec3448b..07286928 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -88,7 +88,6 @@ jobs: files: dist/*.whl body_path: CHANGELOG_RELEASE_BODY.md - name: Patch plugin manifests on main to new tag's wheel URLs - if: startsWith(github.ref_name, 'v') run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" @@ -102,6 +101,8 @@ jobs: tag = os.environ["GITHUB_REF_NAME"] ver = tag.removeprefix("v") + # Tag-to-wheel-version normalization. Covers the prerelease forms used in RELEASE.md + # (`-beta.N`, `-rc.N`, `-alpha.N`); extend here if the release flow adopts others. pep = ver.replace("-beta.", "b").replace("-rc.", "rc").replace("-alpha.", "a") repo = os.environ["GITHUB_REPOSITORY"] base = f"https://github.com/{repo}/releases/download/{tag}" @@ -131,12 +132,15 @@ jobs: p.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") PY git add .mcp.json commands/install.md .claude-plugin/plugin.json .claude-plugin/marketplace.json - if git diff --cached --quiet; then + if ! git diff --cached --quiet; then + git commit -m "chore(release): point plugin manifests at ${GITHUB_REF_NAME} wheels" + git push origin main + else echo "No manifest changes to commit" - exit 0 fi - git commit -m "chore(release): point plugin manifests at ${GITHUB_REF_NAME} wheels" - git push origin main + # Restore the working tree to the tag SHA so any subsequent step that reads tracked + # files sees the release commit, not main's tip. + git checkout "${GITHUB_SHA}" - name: Stage PyPI wheels (SDK + CLI + MCP + Auth + Infra) if: startsWith(github.ref_name, 'v1.') && !contains(github.ref_name, '-') run: | diff --git a/commands/install.md b/commands/install.md index 99866c4d..437d34fa 100644 --- a/commands/install.md +++ b/commands/install.md @@ -1,6 +1,6 @@ --- name: install -description: Install the Pipefy CLI (pipefy-cli + pipefy-auth + pipefy-infra + pipefy-sdk) as a persistent uv tool. +description: Install the Pipefy CLI (pipefy-auth + pipefy-cli + pipefy-infra + pipefy-sdk) as a persistent uv tool. disable-model-invocation: true --- From 6134877c92deea7d719b4874979b621172d6f865 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Mon, 1 Jun 2026 14:07:53 -0300 Subject: [PATCH 3/6] feat(plugin): delegate /pipefy:install to curl installer --- .github/workflows/release.yml | 7 ++- CHANGELOG.md | 2 +- RELEASE.md | 2 +- commands/install.md | 9 ++-- packages/mcp/README.md | 84 +++++++++++++++++++++++++++++++++++ 5 files changed, 93 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07286928..e0635f58 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -115,9 +115,8 @@ jobs: pkg = match.group(1) return f"{base}/{pkg}-{pep}-py3-none-any.whl" - for path in ("commands/install.md", ".mcp.json"): - p = Path(path) - p.write_text(url_pattern.sub(rewrite, p.read_text(encoding="utf-8")), encoding="utf-8") + mcp_json = Path(".mcp.json") + mcp_json.write_text(url_pattern.sub(rewrite, mcp_json.read_text(encoding="utf-8")), encoding="utf-8") for path, key_path in [ (".claude-plugin/plugin.json", ("version",)), @@ -131,7 +130,7 @@ jobs: obj[key_path[-1]] = ver p.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") PY - git add .mcp.json commands/install.md .claude-plugin/plugin.json .claude-plugin/marketplace.json + git add .mcp.json .claude-plugin/plugin.json .claude-plugin/marketplace.json if ! git diff --cached --quiet; then git commit -m "chore(release): point plugin manifests at ${GITHUB_REF_NAME} wheels" git push origin main diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c869362..c09bd49d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - **Auth / SDK**: every `PIPEFY_*` env var is validated against a semantically meaningful pattern at settings construction. URL env vars (`PIPEFY_BASE_URL`, `PIPEFY_AUTH_URL`) require `https?://` plus non-whitespace; credential fields (`PIPEFY_TOKEN`, `PIPEFY_SERVICE_ACCOUNT_CLIENT_ID`, `PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET`, `PIPEFY_AUTH_CLIENT_ID`) reject leading / trailing whitespace; `PIPEFY_ORG_ID` must be an ASCII numeric string. There is no longer an empty-string opt-out for any tier — unset the variable to fall back to the default, or use `PIPEFY_DISABLE_STORED_SESSION=1` to turn the stored-session tier off explicitly (see the **Added** entry above). - **SDK / MCP / CLI**: renamed `--graphql-url` CLI flag to `--base-url` to match the new env-var shape. - **Install snippets**: every `git+https://github.com/gbrlcustodio/pipefy-mcp-server` URL in the root `README.md`, `packages/mcp/README.md`, and `packages/cli/README.md` now pins `@latest` — a moving git tag the release flow updates to point at the most recent release. Users get release-stable installs by default; `@v0.2.0-beta.1`-style pins remain available for reproducibility. The release process documented in `RELEASE.md` now includes `git tag -f latest && git push --force-with-lease origin latest` as the step that rolls new installs forward. -- **Plugin / MCP**: `.mcp.json` and `commands/install.md` resolve through pre-built wheels attached to each GitHub Release (`releases/download/v/pipefy_--py3-none-any.whl`) instead of `uvx --from git+@latest#subdirectory=...`. Drops the full git clone + setuptools build pass on every plugin install or `--refresh`. The release workflow auto-patches `.mcp.json`, `commands/install.md`, `.claude-plugin/plugin.json`, and `.claude-plugin/marketplace.json` to the just-cut tag on each `v*` push and commits the result to `main`. The README MCP-client wiring blocks (Cursor, Claude Desktop, Codex) show the same wheel-URL shape with `` / `` placeholders for manual substitution; the curl installer ([#231](https://github.com/gbrlcustodio/pipefy-mcp-server/issues/231)) will resolve them automatically. Closes #234. +- **Plugin / MCP**: `.mcp.json` resolves through pre-built wheels attached to each GitHub Release (`releases/download/v/pipefy_--py3-none-any.whl`) instead of `uvx --from git+@latest#subdirectory=...`. Drops the full git clone + setuptools build pass on every plugin install or `--refresh`. The release workflow auto-patches `.mcp.json`, `.claude-plugin/plugin.json`, and `.claude-plugin/marketplace.json` to the just-cut tag on each `v*` push and commits the result to `main`. `commands/install.md` (the `/pipefy:install` slash command) now delegates to `install.sh`, which resolves the latest release at runtime, so it no longer needs per-tag rewriting. The paste-into-config MCP-client wiring blocks (Cursor, Claude Desktop, Codex) move from the root `README.md` into `packages/mcp/README.md`'s **Edge cases and alternative wiring** section with `` / `` placeholders for manual substitution; the curl installer ([#231](https://github.com/gbrlcustodio/pipefy-mcp-server/issues/231)) is the recommended path and writes these configs automatically. Closes #234. ### Deprecated diff --git a/RELEASE.md b/RELEASE.md index 97973364..fe982bac 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -42,7 +42,7 @@ The Release workflow requires the git tag (without leading `v`) to **exactly mat git tag -f latest vX.Y.Z && git push --force-with-lease origin latest ``` -7. Wait for the **Release** workflow (`.github/workflows/release.yml`) to finish. As part of the run it auto-commits a `chore(release): point plugin manifests at vX.Y.Z wheels` patch to `main`, pointing `.mcp.json`, `commands/install.md`, `.claude-plugin/plugin.json`, and `.claude-plugin/marketplace.json` at the just-uploaded wheels. The next `dev → main` release PR will conflict on these four files; resolve by taking dev's version (it already carries the bump for the next beta) and letting the workflow re-patch on tag push. +7. Wait for the **Release** workflow (`.github/workflows/release.yml`) to finish. As part of the run it auto-commits a `chore(release): point plugin manifests at vX.Y.Z wheels` patch to `main`, pointing `.mcp.json`, `.claude-plugin/plugin.json`, and `.claude-plugin/marketplace.json` at the just-uploaded wheels. The next `dev → main` release PR will conflict on these three files; resolve by taking dev's version (it already carries the bump for the next beta) and letting the workflow re-patch on tag push. 8. Confirm the GitHub Release lists the built wheels (`pipefy_cli-*.whl`, `pipefy_mcp_server-*.whl`, `pipefy_sdk-*.whl`, `pipefy_auth-*.whl`, and `pipefy_infra-*.whl`). Optionally verify install from the tag, for example: ```bash diff --git a/commands/install.md b/commands/install.md index 437d34fa..3a95cbb6 100644 --- a/commands/install.md +++ b/commands/install.md @@ -9,11 +9,10 @@ If `command -v pipefy` succeeds, surface `pipefy --version` and stop. Otherwise prompt the user to confirm running: ``` -uv tool install --force \ - --with https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download/v0.2.0-beta.2/pipefy_sdk-0.2.0b2-py3-none-any.whl \ - --with https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download/v0.2.0-beta.2/pipefy_auth-0.2.0b2-py3-none-any.whl \ - --with https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download/v0.2.0-beta.2/pipefy_infra-0.2.0b2-py3-none-any.whl \ - https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download/v0.2.0-beta.2/pipefy_cli-0.2.0b2-py3-none-any.whl +curl -fsSL https://raw.githubusercontent.com/gbrlcustodio/pipefy-mcp-server/main/install.sh \ + | sh -s -- --yes --no-skills --client none ``` +The installer resolves the latest GitHub Release at runtime and runs `uv tool install` with the discovered wheel URLs. `--client none` skips MCP-client config writes; the Claude Code plugin's `.mcp.json` already wires the MCP server. + Verify with `pipefy --version`. diff --git a/packages/mcp/README.md b/packages/mcp/README.md index f57a292e..867aa956 100644 --- a/packages/mcp/README.md +++ b/packages/mcp/README.md @@ -106,6 +106,90 @@ If you have a clone of this repo and want the MCP server to use it directly (wit This form also works as a per-project `.mcp.json` if your team shares a clone. Committing `.mcp.json` without secrets (placeholders or env injection) keeps team setups consistent. +### Cursor / Claude Desktop / Codex: paste-into-config wiring + +The root `README.md`'s **Quick install** section is the recommended path (the curl installer writes these configs for you). The blocks below document the shape for users who prefer to wire by hand. Replace `` with the latest release tag (e.g. `v0.2.0-beta.2`) and `` with its PEP 440 wheel form (e.g. `0.2.0b2`); see [Releases](https://github.com/gbrlcustodio/pipefy-mcp-server/releases) for current values. + +#### Cursor + +Edit `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per project): + +```json +{ + "mcpServers": { + "pipefy": { + "command": "uvx", + "args": [ + "--with", + "https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download//pipefy_sdk--py3-none-any.whl", + "--with", + "https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download//pipefy_auth--py3-none-any.whl", + "--with", + "https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download//pipefy_infra--py3-none-any.whl", + "--from", + "https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download//pipefy_mcp_server--py3-none-any.whl", + "pipefy-mcp-server" + ], + "env": { + "PIPEFY_SERVICE_ACCOUNT_CLIENT_ID": "", + "PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET": "" + } + } + } +} +``` + +#### Claude Desktop + +Config file location: + +| OS | Path | +|----|------| +| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` | +| Windows | `%APPDATA%\Claude\claude_desktop_config.json` | + +```json +{ + "mcpServers": { + "pipefy": { + "command": "uvx", + "args": [ + "--with", + "https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download//pipefy_sdk--py3-none-any.whl", + "--with", + "https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download//pipefy_auth--py3-none-any.whl", + "--with", + "https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download//pipefy_infra--py3-none-any.whl", + "--from", + "https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download//pipefy_mcp_server--py3-none-any.whl", + "pipefy-mcp-server" + ], + "env": { + "PIPEFY_SERVICE_ACCOUNT_CLIENT_ID": "", + "PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET": "" + } + } + } +} +``` + +#### Codex + +Edit `~/.codex/config.toml`: + +```toml +[mcp_servers.pipefy] +command = "uvx" +args = [ + "--with", "https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download//pipefy_sdk--py3-none-any.whl", + "--with", "https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download//pipefy_auth--py3-none-any.whl", + "--with", "https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download//pipefy_infra--py3-none-any.whl", + "--from", "https://github.com/gbrlcustodio/pipefy-mcp-server/releases/download//pipefy_mcp_server--py3-none-any.whl", + "pipefy-mcp-server", +] +env = { PIPEFY_SERVICE_ACCOUNT_CLIENT_ID = "", PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET = "" } +``` + ### Legacy environment variables `PIPEFY_OAUTH_CLIENT` and `PIPEFY_OAUTH_SECRET` still resolve to the new `PIPEFY_SERVICE_ACCOUNT_*` names with a one-shot stderr deprecation warning. The aliases will be removed in a later `0.2.0-beta.x` release. The `PIPEFY_OAUTH_URL` alias was dropped — set `PIPEFY_BASE_URL` instead. Migration notes: [`docs/MIGRATION.md#service-account-env-var-rename`](../../docs/MIGRATION.md#service-account-env-var-rename). From 4f22047718ec07ad843f2bafd9261ddaa6ab628f Mon Sep 17 00:00:00 2001 From: Gabriel Date: Mon, 1 Jun 2026 15:17:02 -0300 Subject: [PATCH 4/6] =?UTF-8?q?fix(pr-review):=20/simplify=20pass=20on=20P?= =?UTF-8?q?R=20#260=20=E2=80=94=20release=20flow=20&=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - release.yml: serialize tag-push runs (concurrency group), validate tag→PEP-440 normalization against an allowlist instead of silent passthrough, and fail the patch step if `.mcp.json` has zero URL matches (catches a silent no-op when main's URL shape drifts). - README.md: scope the `@latest` / `--with #subdirectory=` claims to the snippets that still use them; describe /pipefy:install as the curl-installer delegate it now is. - packages/mcp/README.md: fix dead "Quick install" cross-reference. - CHANGELOG.md: describe the wiring blocks as duplicated across both READMEs (they weren't moved — b8069bd reverted the move). --- .github/workflows/release.yml | 35 ++++++++++++++++++++++++++++++++--- CHANGELOG.md | 2 +- packages/mcp/README.md | 2 +- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e0635f58..dcfe004d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,13 @@ permissions: contents: write id-token: write +# Serialize tag-push runs so two near-simultaneous releases don't race the +# `git push origin main` in the manifest-patch step (the loser would fail +# non-fast-forward after the GitHub Release is already published). +concurrency: + group: release-main-patch + cancel-in-progress: false + jobs: release: runs-on: ubuntu-latest @@ -97,13 +104,27 @@ jobs: import json import os import re + import sys from pathlib import Path tag = os.environ["GITHUB_REF_NAME"] ver = tag.removeprefix("v") # Tag-to-wheel-version normalization. Covers the prerelease forms used in RELEASE.md - # (`-beta.N`, `-rc.N`, `-alpha.N`); extend here if the release flow adopts others. - pep = ver.replace("-beta.", "b").replace("-rc.", "rc").replace("-alpha.", "a") + # (`-beta.N`, `-rc.N`, `-alpha.N`); any other shape fails loudly so a future + # tag form doesn't silently produce a URL that points at a non-existent wheel. + prerelease = {"beta": "b", "rc": "rc", "alpha": "a"} + stable = re.fullmatch(r"\d+(?:\.\d+)*", ver) + pre = re.fullmatch(r"(\d+(?:\.\d+)*)-(beta|rc|alpha)\.(\d+)", ver) + if stable: + pep = ver + elif pre: + pep = f"{pre.group(1)}{prerelease[pre.group(2)]}{pre.group(3)}" + else: + sys.exit( + f"Tag {tag!r} not recognized: expected `vN.N.N` or `vN.N.N-{{beta,rc,alpha}}.N`. " + "Extend the normalization in .github/workflows/release.yml if the release flow " + "adopts a new prerelease form." + ) repo = os.environ["GITHUB_REPOSITORY"] base = f"https://github.com/{repo}/releases/download/{tag}" @@ -116,7 +137,15 @@ jobs: return f"{base}/{pkg}-{pep}-py3-none-any.whl" mcp_json = Path(".mcp.json") - mcp_json.write_text(url_pattern.sub(rewrite, mcp_json.read_text(encoding="utf-8")), encoding="utf-8") + old = mcp_json.read_text(encoding="utf-8") + new, n = url_pattern.subn(rewrite, old) + if n == 0: + sys.exit( + f".mcp.json on main has no wheel URLs matching {url_pattern.pattern!r}; " + "the release flow expects releases/download/v/pipefy_-*.whl URLs " + "already in place (a no-op rewrite would silently ship stale URLs)." + ) + mcp_json.write_text(new, encoding="utf-8") for path, key_path in [ (".claude-plugin/plugin.json", ("version",)), diff --git a/CHANGELOG.md b/CHANGELOG.md index c09bd49d..b06518f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - **Auth / SDK**: every `PIPEFY_*` env var is validated against a semantically meaningful pattern at settings construction. URL env vars (`PIPEFY_BASE_URL`, `PIPEFY_AUTH_URL`) require `https?://` plus non-whitespace; credential fields (`PIPEFY_TOKEN`, `PIPEFY_SERVICE_ACCOUNT_CLIENT_ID`, `PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET`, `PIPEFY_AUTH_CLIENT_ID`) reject leading / trailing whitespace; `PIPEFY_ORG_ID` must be an ASCII numeric string. There is no longer an empty-string opt-out for any tier — unset the variable to fall back to the default, or use `PIPEFY_DISABLE_STORED_SESSION=1` to turn the stored-session tier off explicitly (see the **Added** entry above). - **SDK / MCP / CLI**: renamed `--graphql-url` CLI flag to `--base-url` to match the new env-var shape. - **Install snippets**: every `git+https://github.com/gbrlcustodio/pipefy-mcp-server` URL in the root `README.md`, `packages/mcp/README.md`, and `packages/cli/README.md` now pins `@latest` — a moving git tag the release flow updates to point at the most recent release. Users get release-stable installs by default; `@v0.2.0-beta.1`-style pins remain available for reproducibility. The release process documented in `RELEASE.md` now includes `git tag -f latest && git push --force-with-lease origin latest` as the step that rolls new installs forward. -- **Plugin / MCP**: `.mcp.json` resolves through pre-built wheels attached to each GitHub Release (`releases/download/v/pipefy_--py3-none-any.whl`) instead of `uvx --from git+@latest#subdirectory=...`. Drops the full git clone + setuptools build pass on every plugin install or `--refresh`. The release workflow auto-patches `.mcp.json`, `.claude-plugin/plugin.json`, and `.claude-plugin/marketplace.json` to the just-cut tag on each `v*` push and commits the result to `main`. `commands/install.md` (the `/pipefy:install` slash command) now delegates to `install.sh`, which resolves the latest release at runtime, so it no longer needs per-tag rewriting. The paste-into-config MCP-client wiring blocks (Cursor, Claude Desktop, Codex) move from the root `README.md` into `packages/mcp/README.md`'s **Edge cases and alternative wiring** section with `` / `` placeholders for manual substitution; the curl installer ([#231](https://github.com/gbrlcustodio/pipefy-mcp-server/issues/231)) is the recommended path and writes these configs automatically. Closes #234. +- **Plugin / MCP**: `.mcp.json` resolves through pre-built wheels attached to each GitHub Release (`releases/download/v/pipefy_--py3-none-any.whl`) instead of `uvx --from git+@latest#subdirectory=...`. Drops the full git clone + setuptools build pass on every plugin install or `--refresh`. The release workflow auto-patches `.mcp.json`, `.claude-plugin/plugin.json`, and `.claude-plugin/marketplace.json` to the just-cut tag on each `v*` push and commits the result to `main`. `commands/install.md` (the `/pipefy:install` slash command) now delegates to `install.sh`, which resolves the latest release at runtime, so it no longer needs per-tag rewriting. The paste-into-config MCP-client wiring blocks (Cursor, Claude Desktop, Codex) switch to release-wheel URLs with `` / `` placeholders in both the root `README.md` and `packages/mcp/README.md`'s new **Cursor / Claude Desktop / Codex: paste-into-config wiring** subsection; the curl installer ([#231](https://github.com/gbrlcustodio/pipefy-mcp-server/issues/231)) is the recommended path and writes these configs automatically. Closes #234. ### Deprecated diff --git a/packages/mcp/README.md b/packages/mcp/README.md index 867aa956..f233c04d 100644 --- a/packages/mcp/README.md +++ b/packages/mcp/README.md @@ -108,7 +108,7 @@ This form also works as a per-project `.mcp.json` if your team shares a clone. C ### Cursor / Claude Desktop / Codex: paste-into-config wiring -The root `README.md`'s **Quick install** section is the recommended path (the curl installer writes these configs for you). The blocks below document the shape for users who prefer to wire by hand. Replace `` with the latest release tag (e.g. `v0.2.0-beta.2`) and `` with its PEP 440 wheel form (e.g. `0.2.0b2`); see [Releases](https://github.com/gbrlcustodio/pipefy-mcp-server/releases) for current values. +The [curl installer](https://github.com/gbrlcustodio/pipefy-mcp-server/issues/231) writes these configs for you. The blocks below document the shape for users who prefer to wire by hand. Replace `` with the latest release tag (e.g. `v0.2.0-beta.2`) and `` with its PEP 440 wheel form (e.g. `0.2.0b2`); see [Releases](https://github.com/gbrlcustodio/pipefy-mcp-server/releases) for current values. #### Cursor From a47f418a086c6dc48708b8a1643204d4795e31ea Mon Sep 17 00:00:00 2001 From: Gabriel Date: Tue, 2 Jun 2026 16:18:20 -0300 Subject: [PATCH 5/6] fix(pr-review): scope changelog wheel-block note to packages/mcp/README --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b06518f4..674ccbd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - **Auth / SDK**: every `PIPEFY_*` env var is validated against a semantically meaningful pattern at settings construction. URL env vars (`PIPEFY_BASE_URL`, `PIPEFY_AUTH_URL`) require `https?://` plus non-whitespace; credential fields (`PIPEFY_TOKEN`, `PIPEFY_SERVICE_ACCOUNT_CLIENT_ID`, `PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET`, `PIPEFY_AUTH_CLIENT_ID`) reject leading / trailing whitespace; `PIPEFY_ORG_ID` must be an ASCII numeric string. There is no longer an empty-string opt-out for any tier — unset the variable to fall back to the default, or use `PIPEFY_DISABLE_STORED_SESSION=1` to turn the stored-session tier off explicitly (see the **Added** entry above). - **SDK / MCP / CLI**: renamed `--graphql-url` CLI flag to `--base-url` to match the new env-var shape. - **Install snippets**: every `git+https://github.com/gbrlcustodio/pipefy-mcp-server` URL in the root `README.md`, `packages/mcp/README.md`, and `packages/cli/README.md` now pins `@latest` — a moving git tag the release flow updates to point at the most recent release. Users get release-stable installs by default; `@v0.2.0-beta.1`-style pins remain available for reproducibility. The release process documented in `RELEASE.md` now includes `git tag -f latest && git push --force-with-lease origin latest` as the step that rolls new installs forward. -- **Plugin / MCP**: `.mcp.json` resolves through pre-built wheels attached to each GitHub Release (`releases/download/v/pipefy_--py3-none-any.whl`) instead of `uvx --from git+@latest#subdirectory=...`. Drops the full git clone + setuptools build pass on every plugin install or `--refresh`. The release workflow auto-patches `.mcp.json`, `.claude-plugin/plugin.json`, and `.claude-plugin/marketplace.json` to the just-cut tag on each `v*` push and commits the result to `main`. `commands/install.md` (the `/pipefy:install` slash command) now delegates to `install.sh`, which resolves the latest release at runtime, so it no longer needs per-tag rewriting. The paste-into-config MCP-client wiring blocks (Cursor, Claude Desktop, Codex) switch to release-wheel URLs with `` / `` placeholders in both the root `README.md` and `packages/mcp/README.md`'s new **Cursor / Claude Desktop / Codex: paste-into-config wiring** subsection; the curl installer ([#231](https://github.com/gbrlcustodio/pipefy-mcp-server/issues/231)) is the recommended path and writes these configs automatically. Closes #234. +- **Plugin / MCP**: `.mcp.json` resolves through pre-built wheels attached to each GitHub Release (`releases/download/v/pipefy_--py3-none-any.whl`) instead of `uvx --from git+@latest#subdirectory=...`. Drops the full git clone + setuptools build pass on every plugin install or `--refresh`. The release workflow auto-patches `.mcp.json`, `.claude-plugin/plugin.json`, and `.claude-plugin/marketplace.json` to the just-cut tag on each `v*` push and commits the result to `main`. `commands/install.md` (the `/pipefy:install` slash command) now delegates to `install.sh`, which resolves the latest release at runtime, so it no longer needs per-tag rewriting. The paste-into-config MCP-client wiring blocks (Cursor, Claude Desktop, Codex) switch to release-wheel URLs with `` / `` placeholders in `packages/mcp/README.md`'s new **Cursor / Claude Desktop / Codex: paste-into-config wiring** subsection; the curl installer ([#231](https://github.com/gbrlcustodio/pipefy-mcp-server/issues/231)) is the recommended path and writes these configs automatically. Closes #234. ### Deprecated From 270e7db095e2eb95d53f306efbb4cde4fe4f66e2 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Tue, 2 Jun 2026 16:18:53 -0300 Subject: [PATCH 6/6] fix(pr-review): align root README /pipefy:install note with install.sh delegation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bf42e7fb..504ebffa 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ After install, run `pipefy auth login` to authenticate (`--device` on headless s /pipefy:login ``` -`/plugin install pipefy` registers the MCP server and the `/pipefy:install` + `/pipefy:login` slash commands. `/pipefy:install` runs `uv tool install` once to put `pipefy` on PATH (idempotent). `/pipefy:login` runs the OAuth browser flow. For hand-wired setups (paste-into-config blocks per client, the macOS `errSecParam` keychain note, the local-clone alternative for contributors), see [`packages/mcp/README.md`](packages/mcp/README.md). +`/plugin install pipefy` registers the MCP server and the `/pipefy:install` + `/pipefy:login` slash commands. `/pipefy:install` runs the `install.sh` installer (resolves the latest GitHub Release and installs its wheels via `uv tool install`) to put `pipefy` on PATH (idempotent). `/pipefy:login` runs the OAuth browser flow. For hand-wired setups (paste-into-config blocks per client, the macOS `errSecParam` keychain note, the local-clone alternative for contributors), see [`packages/mcp/README.md`](packages/mcp/README.md). ### CLI