Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -87,6 +94,81 @@ jobs:
with:
files: dist/*.whl
body_path: CHANGELOG_RELEASE_BODY.md
- name: Patch plugin manifests on main to new tag's wheel URLs
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
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`); 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}"

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"

mcp_json = Path(".mcp.json")
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<tag>/pipefy_<pkg>-*.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",)),
(".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 .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
else
echo "No manifest changes to commit"
fi
# 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: |
Expand Down
8 changes: 5 additions & 3 deletions .mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
"command": "uvx",
"args": [
"--with",
"pipefy-sdk @ git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/sdk",
"https://github.com/pipefy/ai-toolkit/releases/download/v0.2.0-beta.2/pipefy_sdk-0.2.0b2-py3-none-any.whl",
"--with",
"pipefy-auth @ git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/auth",
"https://github.com/pipefy/ai-toolkit/releases/download/v0.2.0-beta.2/pipefy_auth-0.2.0b2-py3-none-any.whl",
"--with",
"https://github.com/pipefy/ai-toolkit/releases/download/v0.2.0-beta.2/pipefy_infra-0.2.0b2-py3-none-any.whl",
"--from",
"git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/mcp",
"https://github.com/pipefy/ai-toolkit/releases/download/v0.2.0-beta.2/pipefy_mcp_server-0.2.0b2-py3-none-any.whl",
"pipefy-mcp-server"
]
}
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <version> && 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 <version> && 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<tag>/pipefy_<pkg>-<ver>-py3-none-any.whl`) instead of `uvx --from git+<repo>@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 `<TAG>` / `<VERSION>` 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

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,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

Expand Down
4 changes: 2 additions & 2 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`, `.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
Expand Down
10 changes: 5 additions & 5 deletions commands/install.md
Original file line number Diff line number Diff line change
@@ -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-auth + pipefy-cli + pipefy-infra + pipefy-sdk) as a persistent uv tool.
disable-model-invocation: true
---

Expand All @@ -9,10 +9,10 @@ If `command -v pipefy` succeeds, surface `pipefy --version` and stop.
Otherwise prompt the user to confirm running:

```
uv tool install --force \
--with "pipefy-sdk @ git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/sdk" \
--with "pipefy-auth @ git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/auth" \
"git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/cli"
curl -fsSL https://raw.githubusercontent.com/pipefy/ai-toolkit/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`.
84 changes: 84 additions & 0 deletions packages/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 [curl installer](https://github.com/pipefy/ai-toolkit/issues/231) writes these configs for you. The blocks below document the shape for users who prefer to wire by hand. Replace `<TAG>` with the latest release tag (e.g. `v0.2.0-beta.2`) and `<VERSION>` with its PEP 440 wheel form (e.g. `0.2.0b2`); see [Releases](https://github.com/pipefy/ai-toolkit/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/pipefy/ai-toolkit/releases/download/<TAG>/pipefy_sdk-<VERSION>-py3-none-any.whl",
"--with",
"https://github.com/pipefy/ai-toolkit/releases/download/<TAG>/pipefy_auth-<VERSION>-py3-none-any.whl",
"--with",
"https://github.com/pipefy/ai-toolkit/releases/download/<TAG>/pipefy_infra-<VERSION>-py3-none-any.whl",
"--from",
"https://github.com/pipefy/ai-toolkit/releases/download/<TAG>/pipefy_mcp_server-<VERSION>-py3-none-any.whl",
"pipefy-mcp-server"
],
"env": {
"PIPEFY_SERVICE_ACCOUNT_CLIENT_ID": "<CLIENT_ID>",
"PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET": "<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/pipefy/ai-toolkit/releases/download/<TAG>/pipefy_sdk-<VERSION>-py3-none-any.whl",
"--with",
"https://github.com/pipefy/ai-toolkit/releases/download/<TAG>/pipefy_auth-<VERSION>-py3-none-any.whl",
"--with",
"https://github.com/pipefy/ai-toolkit/releases/download/<TAG>/pipefy_infra-<VERSION>-py3-none-any.whl",
"--from",
"https://github.com/pipefy/ai-toolkit/releases/download/<TAG>/pipefy_mcp_server-<VERSION>-py3-none-any.whl",
"pipefy-mcp-server"
],
"env": {
"PIPEFY_SERVICE_ACCOUNT_CLIENT_ID": "<CLIENT_ID>",
"PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET": "<CLIENT_SECRET>"
}
}
}
}
```

#### Codex

Edit `~/.codex/config.toml`:

```toml
[mcp_servers.pipefy]
command = "uvx"
args = [
"--with", "https://github.com/pipefy/ai-toolkit/releases/download/<TAG>/pipefy_sdk-<VERSION>-py3-none-any.whl",
"--with", "https://github.com/pipefy/ai-toolkit/releases/download/<TAG>/pipefy_auth-<VERSION>-py3-none-any.whl",
"--with", "https://github.com/pipefy/ai-toolkit/releases/download/<TAG>/pipefy_infra-<VERSION>-py3-none-any.whl",
"--from", "https://github.com/pipefy/ai-toolkit/releases/download/<TAG>/pipefy_mcp_server-<VERSION>-py3-none-any.whl",
"pipefy-mcp-server",
]
env = { PIPEFY_SERVICE_ACCOUNT_CLIENT_ID = "<CLIENT_ID>", PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET = "<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).
Expand Down
Loading