diff --git a/.github/scripts/update_formula.sh b/.github/scripts/update_formula.sh index 1598666..2a7258e 100755 --- a/.github/scripts/update_formula.sh +++ b/.github/scripts/update_formula.sh @@ -4,35 +4,39 @@ set -o errexit PACKAGE_NAME=$1 PACKAGE_VERSION=$2 +# The NPM package name, when it differs from the formula name — e.g. a scoped +# package (`@apify/mcpc`) cannot be a formula name. +NPM_PACKAGE_NAME=${3:-$PACKAGE_NAME} -if [[ "$#" -ne 2 ]]; then - echo "Usage: $0 package-name package-version" +if [[ "$#" -lt 2 || "$#" -gt 3 ]]; then + echo "Usage: $0 package-name package-version [npm-package-name]" exit 1; fi # Run in the Formula directory cd "$(dirname "$0")"/../../Formula -PACKAGE_DEFINITION_URL="https://registry.npmjs.org/${PACKAGE_NAME}/${PACKAGE_VERSION}" +PACKAGE_DEFINITION_URL="https://registry.npmjs.org/${NPM_PACKAGE_NAME}/${PACKAGE_VERSION}" # It can happen that the package is not available right after the publish command finishes # Try waiting 3 minutes until the package version is available for _i in {1..6}; do curl -sf "${PACKAGE_DEFINITION_URL}" &> /dev/null && break; - echo "Package ${PACKAGE_NAME} version ${PACKAGE_VERSION} is not available yet." + echo "Package ${NPM_PACKAGE_NAME} version ${PACKAGE_VERSION} is not available yet." echo "Will retry in 30 seconds." sleep 30; done # Get the tarball URL from the package definition on NPM TARBALL_URL=$(curl -sf "${PACKAGE_DEFINITION_URL}" | jq -r '.dist.tarball') \ - || { echo "Package ${PACKAGE_NAME} version ${PACKAGE_VERSION} is not available."; exit 1; }; + || { echo "Package ${NPM_PACKAGE_NAME} version ${PACKAGE_VERSION} is not available."; exit 1; }; # Calculate the SHA256 hash of the tarball SHA256=$(curl -sf "${TARBALL_URL}" | sha256sum | cut -d " " -f 1) # Replace the URL and the hash in the formula definition -# We have to use `@` as the sed command separator because URLs contain `/` -sed -i.bak -e "s@ url .*@ url \"${TARBALL_URL}\"@" "${PACKAGE_NAME}.rb" -sed -i.bak -e "s@ sha256 .*@ sha256 \"${SHA256}\"@" "${PACKAGE_NAME}.rb" +# We have to use a sed command separator that URLs cannot contain: `/` appears in +# every URL and `@` in the tarball URL of a scoped package (`@apify/mcpc`). +sed -i.bak -e "s| url .*| url \"${TARBALL_URL}\"|" "${PACKAGE_NAME}.rb" +sed -i.bak -e "s| sha256 .*| sha256 \"${SHA256}\"|" "${PACKAGE_NAME}.rb" rm -rf "${PACKAGE_NAME}.rb.bak" diff --git a/.github/workflows/update_formula.yaml b/.github/workflows/update_formula.yaml index da83f55..c4c7f4d 100644 --- a/.github/workflows/update_formula.yaml +++ b/.github/workflows/update_formula.yaml @@ -11,6 +11,10 @@ on: description: 'Package version' required: true type: string + npm_package: + description: 'NPM package name, when it differs from the formula name (e.g. @apify/mcpc)' + required: false + type: string workflow_call: inputs: @@ -22,6 +26,10 @@ on: description: 'Package version' required: true type: string + npm_package: + description: 'NPM package name, when it differs from the formula name (e.g. @apify/mcpc)' + required: false + type: string jobs: test-updated-formula: @@ -39,7 +47,7 @@ jobs: uses: Homebrew/actions/setup-homebrew@master - name: Update package in formula - run: ./.github/scripts/update_formula.sh ${{ github.event.inputs.package }} ${{ github.event.inputs.version }} + run: ./.github/scripts/update_formula.sh ${{ github.event.inputs.package }} ${{ github.event.inputs.version }} ${{ github.event.inputs.npm_package }} - name: Test updated formula run: | @@ -76,7 +84,7 @@ jobs: run: | set -o errexit - ./.github/scripts/update_formula.sh ${{ github.event.inputs.package }} ${{ github.event.inputs.version }} + ./.github/scripts/update_formula.sh ${{ github.event.inputs.package }} ${{ github.event.inputs.version }} ${{ github.event.inputs.npm_package }} git config user.name 'Apify Service Account' git config user.email '64261774+apify-service-account@users.noreply.github.com' diff --git a/Formula/mcpc.rb b/Formula/mcpc.rb new file mode 100644 index 0000000..7a1bf8c --- /dev/null +++ b/Formula/mcpc.rb @@ -0,0 +1,53 @@ +require "language/node" + +class Mcpc < Formula + include Language::Node::Shebang + + desc "Universal command-line client for the Model Context Protocol (MCP)" + homepage "https://github.com/apify/mcpc" + url "https://registry.npmjs.org/@apify/mcpc/-/mcpc-0.5.1.tgz" + sha256 "12732e94654d10a99a37aa808b6fb0fb39f707a8ae479a39a66a0b1e5051d5cc" + license "Apache-2.0" + + depends_on "node" + + on_linux do + # @napi-rs/keyring reaches the OS keychain through the Secret Service API. + # Without libsecret, mcpc falls back to ~/.mcpc/credentials.json (mode 0600). + depends_on "libsecret" + end + + def install + system "npm", "install", *std_npm_args + # Both executables ship "#!/usr/bin/env node", so a node earlier in PATH + # (nvm, asdf, system) would run mcpc against node_modules installed for + # Homebrew's node — and possibly under a runtime older than the required + # >= 22.12. Point them at Homebrew's node instead. + rewrite_shebang detected_node_shebang, *libexec.glob("lib/node_modules/@apify/mcpc/bin/*") + bin.install_symlink libexec.glob("bin/*") + end + + test do + ENV["MCPC_HOME_DIR"] = testpath/".mcpc" + + assert_match version.to_s, shell_output("#{bin}/mcpc --version") + + # A fresh MCPC_HOME_DIR has no sessions and no auth profiles. + listing = JSON.parse(shell_output("#{bin}/mcpc --json")) + assert_empty listing["sessions"] + assert_empty listing["profiles"] + + # Unknown session: actionable error, exit code 1 (client error). + assert_match "Session not found: @nope", + shell_output("#{bin}/mcpc @nope tools-list 2>&1", 1) + + # Native-addon canary. @napi-rs/keyring is tied to a Node.js ABI, and mcpc + # degrades to file-based credential storage when the addon fails to load — + # so without this check a Node major bump would silently downgrade every + # user's credential storage instead of failing here and asking for a + # revision bump. + keyring = libexec.glob("lib/node_modules/**/@napi-rs/keyring").first + refute_nil keyring, "@napi-rs/keyring is missing from the install" + system Formula["node"].opt_bin/"node", "-e", "require(#{keyring.to_s.inspect})" + end +end