Skip to content

chore: remove upstream Codespaces workflow #9

chore: remove upstream Codespaces workflow

chore: remove upstream Codespaces workflow #9

Workflow file for this run

name: MCP CI
on:
push:
branches: [master]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
pull-requests: read
jobs:
changes:
runs-on: ubuntu-24.04
timeout-minutes: 5
name: Determine need to run MCP checks
outputs:
mcp: ${{ steps.filter.outputs.mcp || 'true' }}
steps:
- uses: actions/checkout@v6
# Not only the MCP folder but any file that generates schema for the mcp folder
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: filter
if: github.event_name != 'push' # Run all tests on master push
with:
filters: |
mcp:
- 'services/mcp/**'
- 'products/**'
- 'ee/**/*'
- 'posthog/**/*'
- '.github/workflows/ci-mcp.yml'
- '.github/workflows/mcp-publish.yml'
build:
name: Build Package
runs-on: ubuntu-latest
timeout-minutes: 10
needs: changes
if: needs.changes.outputs.mcp == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: 3.12.12
- name: Install uv
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
with:
version: '0.10.2' # pinned: unpinned setup-uv calls GH API on every job, exhausts rate limit
enable-cache: true
cache-dependency-glob: uv.lock
save-cache: ${{ github.ref == 'refs/heads/master' }}
- name: Install Python dependencies
run: UV_PROJECT_ENVIRONMENT=$pythonLocation uv sync --frozen --dev
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Fix node-gyp permissions
run: chmod +x ~/setup-pnpm/node_modules/.pnpm/pnpm@*/node_modules/pnpm/dist/node_modules/node-gyp/gyp/gyp_main.py
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: .nvmrc
cache: 'pnpm'
- name: Install dependencies
run: pnpm --filter=@posthog/mcp... --filter='./products/*' install --frozen-lockfile
- name: Build package
run: cd services/mcp && pnpm build
- name: Type check
run: cd services/mcp && pnpm typecheck
- name: Check MCP schema is up to date
run: |
./bin/hogli build:schema-mcp
if ! git diff --exit-code services/mcp/schema/tool-inputs.json; then
echo ""
echo "::error::MCP tool-inputs.json is out of date. Run 'hogli build:schema-mcp' and commit the result."
exit 1
fi
- name: Check generated UI apps are up to date
run: |
cd services/mcp && pnpm run generate:ui-apps
if ! git diff --exit-code -- src/ui-apps/apps/generated/ src/resources/ui-apps.generated.ts; then
echo ""
echo "::warning::The following generated files are out of date:"
git diff --name-only -- src/ui-apps/apps/generated/ src/resources/ui-apps.generated.ts
echo ""
echo "::error::Generated UI app files are out of date. Run 'pnpm --filter=@posthog/mcp run generate:ui-apps' and commit the result."
exit 1
fi
- name: Lint tool names
run: cd services/mcp && pnpm lint-tool-names
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 5
needs: changes
if: needs.changes.outputs.mcp == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Fix node-gyp permissions
run: chmod +x ~/setup-pnpm/node_modules/.pnpm/pnpm@*/node_modules/pnpm/dist/node_modules/node-gyp/gyp/gyp_main.py
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: .nvmrc
cache: 'pnpm'
- name: Install dependencies
run: pnpm --filter=@posthog/mcp... --filter='./products/*' install --frozen-lockfile
- name: Run unit tests
run: |
pnpm --filter=@posthog/mcp run test -u
if [ -n "$(git status --porcelain -- services/mcp/tests/unit)" ]; then
echo ""
git status --short -- services/mcp/tests/unit
echo "::error::MCP unit test artifacts are out of date. Run 'pnpm --filter=@posthog/mcp run test -u' and commit the result."
exit 1
fi
- name: Create snapshot patch
id: create-snapshot-patch
if: ${{ always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && !contains(github.actor, '[bot]') && github.actor != 'posthog-bot' && !contains(github.actor, 'github-actions') }}
run: |
SNAPSHOT_PATH="services/mcp/tests/unit"
if [ -z "$(git status --porcelain -- "$SNAPSHOT_PATH")" ]; then
echo "No MCP unit test changes"
echo "has-changes=false" >> $GITHUB_OUTPUT
else
echo "MCP unit test changes detected:"
git status --short -- "$SNAPSHOT_PATH"
git add -N "$SNAPSHOT_PATH"
mkdir -p /tmp/patches
git diff --binary --full-index "$SNAPSHOT_PATH" > /tmp/patches/mcp-unit-tests.patch
echo "has-changes=true" >> $GITHUB_OUTPUT
fi
- name: Upload snapshot patch
if: ${{ steps.create-snapshot-patch.outputs.has-changes == 'true' && always() }}
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: mcp-snapshot-patch
path: /tmp/patches/mcp-unit-tests.patch
retention-days: 1
if-no-files-found: ignore
integration-tests:
name: Integration Tests
runs-on: depot-ubuntu-latest-4
needs: changes
timeout-minutes: 30
if: needs.changes.outputs.mcp == 'true'
env:
OPT_OUT_CAPTURE: 1
SECRET_KEY: '6b01eee4f945ca25045b5aab440b953461faf08693a9abbf1166dc7c6b9772da'
DATABASE_URL: 'postgres://posthog:posthog@localhost:5432/posthog'
PERSONS_DB_WRITER_URL: 'postgres://posthog:posthog@localhost:5432/posthog_persons'
REDIS_URL: 'redis://localhost'
CLICKHOUSE_HOST: 'localhost'
CLICKHOUSE_SECURE: 'False'
CLICKHOUSE_VERIFY: 'False'
OBJECT_STORAGE_ENABLED: 'True'
OBJECT_STORAGE_ENDPOINT: 'http://localhost:19000'
OBJECT_STORAGE_ACCESS_KEY_ID: 'object_storage_root_user'
OBJECT_STORAGE_SECRET_ACCESS_KEY: 'object_storage_root_password'
SKIP_SERVICE_VERSION_REQUIREMENTS: 1
NO_RESTART_LOOP: 1
DISABLE_SECURE_SSL_REDIRECT: 'True'
CLICKHOUSE_LOGS_CLUSTER_SECURE: 'False'
CELERY_METRICS_PORT: 8999
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1
- name: Clean up data directories with container permissions
run: |
[ -d "data" ] && docker run --rm -v "$(pwd)/data:/data" alpine sh -c "rm -rf /data/seaweedfs /data/minio" || true
continue-on-error: true
- name: Stop/Start stack with Docker Compose
shell: bash
run: |
cp posthog/user_scripts/latest_user_defined_function.xml docker/clickhouse/user_defined_function.xml
(
max_attempts=3
attempt=1
delay=5
while [ $attempt -le $max_attempts ]; do
echo "Attempt $attempt of $max_attempts to start stack..."
if docker compose -f docker-compose.dev.yml down && \
docker compose -f docker-compose.dev.yml -f docker-compose.profiles.yml up -d; then
echo "Stack started successfully"
exit 0
fi
echo "Failed to start stack on attempt $attempt"
if [ $attempt -lt $max_attempts ]; then
sleep_time=$((delay * 2 ** (attempt - 1)))
echo "Waiting ${sleep_time} seconds before retry..."
sleep $sleep_time
fi
attempt=$((attempt + 1))
done
echo "Failed to start stack after $max_attempts attempts"
exit 1
) &
- name: Add service hostnames to /etc/hosts
shell: bash
run: echo "127.0.0.1 db redis7 kafka clickhouse clickhouse-coordinator objectstorage seaweedfs temporal" | sudo tee -a /etc/hosts
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: 3.12.12
- name: Install uv
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
with:
version: '0.10.2' # pinned: unpinned setup-uv calls GH API on every job, exhausts rate limit
enable-cache: true
cache-dependency-glob: uv.lock
save-cache: ${{ github.ref == 'refs/heads/master' }}
- name: Install SAML (python3-saml) dependencies
shell: bash
run: |
sudo apt-get update && sudo apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-openssl
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
- name: Fix node-gyp permissions
run: chmod +x ~/setup-pnpm/node_modules/.pnpm/pnpm@*/node_modules/pnpm/dist/node_modules/node-gyp/gyp/gyp_main.py
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: .nvmrc
cache: 'pnpm'
- name: Install Python dependencies
shell: bash
run: |
UV_PROJECT_ENVIRONMENT=$pythonLocation uv sync --frozen --dev
- name: Install Node dependencies
run: pnpm --filter=@posthog/mcp... --filter='./products/*' install --frozen-lockfile
- name: Set up needed files
shell: bash
run: |
mkdir -p frontend/dist
touch frontend/dist/index.html
touch frontend/dist/layout.html
touch frontend/dist/exporter.html
- name: Install Rust
uses: dtolnay/rust-toolchain@0b1efabc08b657293548b77fb76cc02d26091c7e
with:
toolchain: 1.91.1
components: cargo
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
shared-key: 'v2-rust-mcp'
workspaces: rust
save-if: ${{ github.ref == 'refs/heads/master' }}
- name: Install sqlx-cli
run: |
cargo install sqlx-cli --version 0.8.0 --features postgres --no-default-features --locked
- name: Start Docker services
shell: bash
env:
COMPOSE_FILE: docker-compose.dev.yml:docker-compose.profiles.yml
run: bin/ci-wait-for-docker launch --down
- name: Wait for Docker services
shell: bash
env:
COMPOSE_FILE: docker-compose.dev.yml:docker-compose.profiles.yml
run: bin/ci-wait-for-docker wait
- name: Run migrations
run: |
# Create persons database and run sqlx migrations first
DATABASE_URL="postgres://posthog:posthog@localhost:5432/posthog_persons" \
sqlx database create
DATABASE_URL="postgres://posthog:posthog@localhost:5432/posthog_persons" \
sqlx migrate run --source rust/persons_migrations/
# Then run Django migrations
python manage.py migrate --noinput
python manage.py migrate_clickhouse
- name: Seed test data
run: python manage.py generate_demo_data --n-clusters 10 --days-past 7 --days-future 0 --skip-materialization --skip-flag-sync --skip-user-product-list
- name: Create API key and extract test IDs
run: |
python -c "
import django, os
os.environ['DJANGO_SETTINGS_MODULE'] = 'posthog.settings'
django.setup()
from posthog.models import Organization, Team, User, EventDefinition
from posthog.models.personal_api_key import PersonalAPIKey
from posthog.models.utils import hash_key_value
org = Organization.objects.first()
team = Team.objects.first()
user = User.objects.first()
PersonalAPIKey.objects.create(
user=user,
label='mcp_ci_api_key',
secure_value=hash_key_value('e2e_demo_api_key'),
scopes=['*'],
)
# Ensure common event definitions exist for MCP integration tests
for event_name in ['\$pageview', '\$pageleave', '\$autocapture', '\$screen']:
EventDefinition.objects.get_or_create(name=event_name, team=team)
print(f'TEST_ORG_ID={org.id}')
print(f'TEST_PROJECT_ID={team.id}')
" >> $GITHUB_ENV
- name: Source celery queues
run: |
source ./bin/celery-queues.env
echo "CELERY_WORKER_QUEUES=$CELERY_WORKER_QUEUES" >> $GITHUB_ENV
- name: Start PostHog web & Celery worker
run: |
python manage.py run_autoreload_celery --type=worker &> /tmp/celery.log &
python -m granian --interface asgi posthog.asgi:application --host 0.0.0.0 --port 8000 --workers 1 &> /tmp/server.log &
- name: Wait for PostHog to be ready
uses: iFaxity/wait-on-action@1fe019e0475491e9e8c4f421b6914ccc3ed8f99c # v1.2.1
with:
resource: http://localhost:8000
timeout: 180000
interval: 2000
verbose: true
- name: Run integration tests
run: cd services/mcp && pnpm run test:integration
env:
TEST_POSTHOG_API_BASE_URL: http://localhost:8000
TEST_POSTHOG_PERSONAL_API_KEY: e2e_demo_api_key
TEST_ORG_ID: ${{ env.TEST_ORG_ID }}
TEST_PROJECT_ID: ${{ env.TEST_PROJECT_ID }}
- name: Show server logs on failure
if: failure()
run: cat /tmp/server.log || true
- name: Show celery logs on failure
if: failure()
run: cat /tmp/celery.log || true
- name: Show docker compose logs on failure
if: failure()
shell: bash
run: docker compose -f docker-compose.dev.yml logs
handle-snapshots:
name: Commit snapshot changes
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [changes, unit-tests]
if: ${{ always() && needs.changes.outputs.mcp == 'true' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && !contains(github.actor, '[bot]') && github.actor != 'posthog-bot' && !contains(github.actor, 'github-actions') }}
permissions:
contents: write
pull-requests: write
steps:
- name: Get app token
id: app-token
uses: getsentry/action-github-app-token@d4b5da6c5e37703f8c3b3e43abb5705b46e159cc # v3.0.0
with:
app_id: ${{ secrets.GH_APP_POSTHOG_TESTS_APP_ID }}
private_key: ${{ secrets.GH_APP_POSTHOG_TESTS_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
token: ${{ steps.app-token.outputs.token }}
fetch-depth: 1
- name: Download snapshot patches
id: download-patches
continue-on-error: true
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
pattern: mcp-snapshot-patch
path: /tmp/snapshot-patches
merge-multiple: true
- name: Check for patches
id: check-patches
run: |
if [ "${{ steps.download-patches.outcome }}" == "failure" ] || [ ! -d /tmp/snapshot-patches ]; then
echo "has-patches=false" >> $GITHUB_OUTPUT
echo "No snapshot patches found"
exit 0
fi
if [ -n "$(find /tmp/snapshot-patches -name '*.patch' -type f -size +0c 2>/dev/null)" ]; then
echo "has-patches=true" >> $GITHUB_OUTPUT
echo "Found patches:"
ls -la /tmp/snapshot-patches/
else
echo "has-patches=false" >> $GITHUB_OUTPUT
echo "Patch files empty or missing - no snapshot changes"
fi
- name: Commit snapshots
if: steps.check-patches.outputs.has-patches == 'true'
uses: ./.github/actions/commit-snapshots
with:
workflow-type: backend
patch-path: /tmp/snapshot-patches
snapshot-path: 'services/mcp/tests/unit/'
commit-message: 'test(mcp): update unit test snapshots'
pr-number: ${{ github.event.pull_request.number }}
repository: ${{ github.repository }}
commit-sha: ${{ github.event.pull_request.head.sha }}
branch-name: ${{ github.event.pull_request.head.ref }}
github-token: ${{ steps.app-token.outputs.token }}
# Collation job for required status check
mcp_tests:
needs: [changes, build, unit-tests, integration-tests, handle-snapshots]
name: MCP Tests Pass
runs-on: ubuntu-latest
timeout-minutes: 5
if: always()
steps:
- name: Check all MCP jobs
run: |
# Fail if change detection itself failed
if [[ "${{ needs.changes.result }}" == "failure" ]]; then
echo "Change detection job failed."
exit 1
fi
# Pass if no MCP changes detected (jobs were skipped)
if [[ "${{ needs.changes.outputs.mcp }}" != "true" ]]; then
echo "MCP checks were skipped (no relevant changes)."
exit 0
fi
if [[ "${{ needs.build.result }}" != "success" && "${{ needs.build.result }}" != "skipped" ]]; then
echo "MCP build failed."
exit 1
fi
if [[ "${{ needs.unit-tests.result }}" != "success" && "${{ needs.unit-tests.result }}" != "skipped" ]]; then
echo "MCP unit tests failed."
exit 1
fi
if [[ "${{ needs.integration-tests.result }}" != "success" && "${{ needs.integration-tests.result }}" != "skipped" ]]; then
echo "MCP integration tests failed."
exit 1
fi
if [[ "${{ needs.handle-snapshots.result }}" == "failure" ]]; then
echo "MCP snapshot commit job failed."
exit 1
fi
echo "All MCP checks passed."