Skip to content
Merged
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
20 changes: 12 additions & 8 deletions .github/scripts/update_formula.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
12 changes: 10 additions & 2 deletions .github/workflows/update_formula.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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: |
Expand Down Expand Up @@ -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'
Expand Down
53 changes: 53 additions & 0 deletions Formula/mcpc.rb
Original file line number Diff line number Diff line change
@@ -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
Loading