Skip to content

Update MCP Coverage

Update MCP Coverage #8

name: Update MCP Coverage
on:
workflow_run:
workflows: [Publish NPM]
types: [completed]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: update-mcp-coverage
cancel-in-progress: false
jobs:
sdk-version:
if: >-
github.repository == 'kernel/kernel-node-sdk' &&
(github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_repository.full_name == github.repository))
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
sha: ${{ steps.version.outputs.sha }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
persist-credentials: false
- name: Resolve published SDK version
id: version
run: |
version=$(jq -er 'select(.name == "@onkernel/sdk") | .version' package.json)
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Skipping non-stable SDK version: $version"
exit 0
fi
# A just-published version can take a few minutes to become installable.
for attempt in $(seq 1 30); do
if [[ "$(npm view "@onkernel/sdk@$version" version 2>/dev/null)" == "$version" ]]; then
break
fi
if [[ "$attempt" == 30 ]]; then
echo "::error::@onkernel/sdk@$version is not available on npm after 15 minutes."
exit 1
fi
sleep 30
done
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
generate:
needs: sdk-version
if: needs.sdk-version.outputs.version != ''
runs-on: ubuntu-latest
timeout-minutes: 55
outputs:
changed: ${{ steps.snapshot.outputs.changed }}
main_sha: ${{ steps.prepare.outputs.main_sha }}
update_sha: ${{ steps.prepare.outputs.update_sha }}
rebased: ${{ steps.prepare.outputs.rebased }}
agent_base_tree: ${{ steps.prepare.outputs.agent_base_tree }}
env:
SDK_VERSION: ${{ needs.sdk-version.outputs.version }}
SDK_SHA: ${{ needs.sdk-version.outputs.sha }}
UPDATE_BRANCH: mcp-coverage-update
services:
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 20
steps:
- name: Checkout updater instructions
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- name: Checkout published SDK
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
ref: ${{ env.SDK_SHA }}
path: sdk
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '22'
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: '1.3.3'
- name: Checkout MCP server
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
repository: kernel/kernel-mcp-server
path: mcp
fetch-depth: 0
persist-credentials: false
- name: Prepare update branch and SDK diff
id: prepare
working-directory: mcp
run: |
git config user.name 'kernel-internal[bot]'
git config user.email '260533166+kernel-internal[bot]@users.noreply.github.com'
echo "main_sha=$(git rev-parse origin/main)" >> "$GITHUB_OUTPUT"
if git show-ref --verify --quiet "refs/remotes/origin/$UPDATE_BRANCH"; then
echo "update_sha=$(git rev-parse "origin/$UPDATE_BRANCH")" >> "$GITHUB_OUTPUT"
git switch --track "origin/$UPDATE_BRANCH"
if ! git rebase origin/main; then
git rebase --abort
# The agent covers everything since main's SDK version, so a bot-only
# branch can be regenerated from main. Human commits must be kept.
if git log --no-merges --format=%ae "origin/main..origin/$UPDATE_BRANCH" \
| grep -qvxF '260533166+kernel-internal[bot]@users.noreply.github.com'; then
echo "::error::$UPDATE_BRANCH has human commits that conflict with main. Resolve the conflicts on that branch, then rerun this workflow."
exit 1
fi
echo "::warning::$UPDATE_BRANCH conflicts with main; regenerating it from main."
git switch -C "$UPDATE_BRANCH" origin/main
else
echo "rebased=true" >> "$GITHUB_OUTPUT"
fi
else
git switch -c "$UPDATE_BRANCH" origin/main
fi
# The agent's changes are published on top of this head, so human
# commits on the update branch keep their content and authorship.
echo "agent_base=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
echo "agent_base_tree=$(git rev-parse 'HEAD^{tree}')" >> "$GITHUB_OUTPUT"
old_version=$(jq -er '.dependencies["@onkernel/sdk"]' package.json)
if [[ "$old_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] &&
[[ "$old_version" != "$SDK_VERSION" ]] &&
[[ "$(printf '%s\n' "$old_version" "$SDK_VERSION" | sort -V | tail -1)" == "$old_version" ]]; then
echo "MCP already uses newer SDK $old_version; skipping stale run."
exit 0
fi
if git -C ../sdk rev-parse --verify "refs/tags/v$old_version" >/dev/null 2>&1; then
git -C ../sdk diff "refs/tags/v$old_version" "$SDK_SHA" -- api.md src/resources > "$RUNNER_TEMP/sdk-diff.patch"
echo "old_date=$(TZ=UTC git -C ../sdk log -1 --date=format-local:%Y-%m-%dT%H:%M:%SZ --format=%cd "refs/tags/v$old_version")" >> "$GITHUB_OUTPUT"
else
printf 'Baseline %s unavailable; audit all of api.md and src/resources.\n' "$old_version" > "$RUNNER_TEMP/sdk-diff.patch"
fi
echo "new_date=$(TZ=UTC git -C ../sdk log -1 --date=format-local:%Y-%m-%dT%H:%M:%SZ --format=%cd "$SDK_SHA")" >> "$GITHUB_OUTPUT"
echo "update=true" >> "$GITHUB_OUTPUT"
- name: Generate API read token
if: steps.prepare.outputs.update == 'true'
id: api-read-token
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
with:
app-id: ${{ secrets.ADMIN_APP_ID }}
private-key: ${{ secrets.ADMIN_APP_PRIVATE_KEY }}
owner: kernel
repositories: kernel
permission-contents: read
- name: Fetch API spec
if: steps.prepare.outputs.update == 'true'
env:
GH_TOKEN: ${{ steps.api-read-token.outputs.token }}
OLD_DATE: ${{ steps.prepare.outputs.old_date }}
NEW_DATE: ${{ steps.prepare.outputs.new_date }}
run: |
main_at() {
gh api -X GET repos/kernel/kernel/commits -f sha=main -f until="$1" -f per_page=1 --jq '.[0].sha // empty'
}
file_at() {
gh api -H 'Accept: application/vnd.github.raw' "repos/kernel/kernel/contents/$1?ref=$2"
}
spec="$RUNNER_TEMP/kernel-api/packages/api/openapi.yaml"
head=$(main_at "$NEW_DATE")
test -n "$head"
mkdir -p "$(dirname "$spec")"
file_at packages/api/openapi.yaml "$head" > "$spec"
base=""
if [[ -n "$OLD_DATE" ]]; then
base=$(main_at "$OLD_DATE")
fi
if [[ -n "$base" ]]; then
file_at packages/api/openapi.yaml "$base" > "$RUNNER_TEMP/openapi-base.yaml"
diff -u --label "openapi.yaml@$base" --label "openapi.yaml@$head" \
"$RUNNER_TEMP/openapi-base.yaml" "$spec" \
> "$RUNNER_TEMP/openapi-diff.patch" || [[ $? -eq 1 ]]
rm "$RUNNER_TEMP/openapi-base.yaml"
else
printf 'Spec baseline unavailable; compare all relevant descriptions in openapi.yaml.\n' > "$RUNNER_TEMP/openapi-diff.patch"
fi
gh api --method DELETE /installation/token
- name: Install Pi
if: steps.prepare.outputs.update == 'true'
env:
PI_TELEMETRY: '0'
run: |
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
pi --version
- name: Update SDK dependency and MCP coverage
if: steps.prepare.outputs.update == 'true'
working-directory: mcp
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
PI_PREFERRED_MODEL: ${{ vars.PI_PREFERRED_MODEL }}
PI_SKIP_VERSION_CHECK: '1'
PI_TELEMETRY: '0'
run: |
: "${PI_PREFERRED_MODEL:?Set the PI_PREFERRED_MODEL organization variable}"
bun add --exact "@onkernel/sdk@$SDK_VERSION"
pi --print --no-session --approve \
--model "$PI_PREFERRED_MODEL" --thinking max \
@../.github/prompts/update-mcp-coverage.md
- name: Snapshot publishable changes
if: steps.prepare.outputs.update == 'true'
id: snapshot
working-directory: mcp
env:
AGENT_BASE: ${{ steps.prepare.outputs.agent_base }}
run: |
git reset -q --soft "$AGENT_BASE"
git add -A
git diff --cached --name-only --no-renames -z \
| bash ../.github/scripts/mcp-update-disallowed-paths.sh > "$RUNNER_TEMP/excluded-paths"
if [[ -s "$RUNNER_TEMP/excluded-paths" ]]; then
echo "::warning::Discarding changes outside the MCP updater allowlist:"
tr '\0' '\n' < "$RUNNER_TEMP/excluded-paths"
GIT_LITERAL_PATHSPECS=1 git reset -q --pathspec-from-file="$RUNNER_TEMP/excluded-paths" --pathspec-file-nul
fi
git diff --cached --binary --no-renames --no-ext-diff --no-textconv > "$RUNNER_TEMP/changes.patch"
git reset -q --hard
git clean -fdq
if [[ -s "$RUNNER_TEMP/changes.patch" ]]; then
git apply --index "$RUNNER_TEMP/changes.patch"
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Verify MCP changes
if: steps.prepare.outputs.update == 'true'
working-directory: mcp
run: |
test -s "$RUNNER_TEMP/mcp-coverage-report.md"
jq -e --arg version "$SDK_VERSION" '.dependencies["@onkernel/sdk"] == $version and
([.overrides, .resolutions, .patchedDependencies] | map((. // {}) | has("@onkernel/sdk")) | any | not)' package.json
grep -qF "\"@onkernel/sdk\": [\"@onkernel/sdk@$SDK_VERSION\", \"\"," bun.lock
bun install --frozen-lockfile
bun run check:managed-auth-app
bunx tsc --noEmit --incremental false
bun test
bun scripts/record-oauth-redis-contract.ts
git diff --exit-code -- fixtures/oauth-redis-recordings.json
git diff --cached --check
- name: Upload validated changes
if: steps.snapshot.outputs.changed == 'true'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: mcp-coverage-update
path: |
${{ runner.temp }}/changes.patch
${{ runner.temp }}/mcp-coverage-report.md
if-no-files-found: error
retention-days: 7
publish:
needs: [sdk-version, generate]
if: needs.generate.outputs.changed == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
env:
SDK_VERSION: ${{ needs.sdk-version.outputs.version }}
MAIN_SHA: ${{ needs.generate.outputs.main_sha }}
UPDATE_SHA: ${{ needs.generate.outputs.update_sha }}
REBASED: ${{ needs.generate.outputs.rebased }}
AGENT_BASE_TREE: ${{ needs.generate.outputs.agent_base_tree }}
UPDATE_BRANCH: mcp-coverage-update
steps:
- name: Checkout updater scripts
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- name: Download validated changes
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: mcp-coverage-update
path: ${{ runner.temp }}/mcp-coverage-update
- name: Checkout MCP server
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
repository: kernel/kernel-mcp-server
path: mcp
fetch-depth: 0
persist-credentials: false
- name: Apply validated changes
working-directory: mcp
run: |
git config user.name 'kernel-internal[bot]'
git config user.email '260533166+kernel-internal[bot]@users.noreply.github.com'
if [[ "$REBASED" == true ]]; then
git switch -c "$UPDATE_BRANCH" "$UPDATE_SHA"
git rebase -q "$MAIN_SHA"
else
git switch -c "$UPDATE_BRANCH" "$MAIN_SHA"
fi
if [[ "$(git rev-parse 'HEAD^{tree}')" != "$AGENT_BASE_TREE" ]]; then
echo "::error::The rebuilt update branch differs from the one the changes were validated on."
exit 1
fi
git apply --index "$RUNNER_TEMP/mcp-coverage-update/changes.patch"
git diff --cached --name-only --no-renames -z \
| bash ../.github/scripts/mcp-update-disallowed-paths.sh > "$RUNNER_TEMP/disallowed-paths"
if [[ -s "$RUNNER_TEMP/disallowed-paths" ]]; then
echo "::error::Validated changes include paths outside the MCP updater allowlist:"
tr '\0' '\n' < "$RUNNER_TEMP/disallowed-paths"
exit 1
fi
if git diff --cached --raw --no-renames | awk '$2 !~ /^(100644|100755|000000)$/ { found = 1 } END { exit !found }'; then
echo "::error::Validated changes may only contain regular files."
exit 1
fi
git commit -q -m "Update Node SDK to $SDK_VERSION and MCP coverage"
- name: Generate MCP repository token
id: app-token
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
with:
app-id: ${{ secrets.ADMIN_APP_ID }}
private-key: ${{ secrets.ADMIN_APP_PRIVATE_KEY }}
owner: kernel
repositories: kernel-mcp-server
permission-contents: write
permission-pull-requests: write
- name: Open or update MCP coverage PR
working-directory: mcp
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
if git diff --quiet origin/main HEAD; then
echo "MCP coverage is already up to date."
exit 0
fi
gh auth setup-git
git push --force-with-lease="$UPDATE_BRANCH:$UPDATE_SHA" origin "$UPDATE_BRANCH"
body="$RUNNER_TEMP/mcp-coverage-pr.md"
{
printf '## Summary\n\n- Update `@onkernel/sdk` to `%s`.\n' "$SDK_VERSION"
printf '\n## Coverage\n\n'
head -c 60000 "$RUNNER_TEMP/mcp-coverage-update/mcp-coverage-report.md"
printf '\n## Validation\n\n- Frozen Bun install\n- Managed-auth App bundle check\n- TypeScript type check\n- Bun tests\n- OAuth Redis recording check\n'
} > "$body"
pr=$(gh pr list --repo kernel/kernel-mcp-server --head "$UPDATE_BRANCH" --base main \
--state open --json number --jq '.[0].number // empty')
title="MCP: Update Node SDK to $SDK_VERSION"
if [[ -n "$pr" ]]; then
gh pr edit "$pr" --repo kernel/kernel-mcp-server --title "$title" --body-file "$body"
else
gh pr create --repo kernel/kernel-mcp-server --head "$UPDATE_BRANCH" --base main \
--title "$title" --body-file "$body"
fi