📡 fix: Refresh MCP Tools After List-Changed Notifications (#14686)

* fix(mcp): handle dynamic tool list changes

Co-authored-by: Pascal Garber <pascal@artandcode.studio>

* test(mcp): fix CI validation

* fix(mcp): keep dynamic tool catalogs live

* fix(mcp): harden dynamic catalog lifecycle

* test(mcp): use typed startup connection

* test(mcp): isolate dynamic e2e fixtures

* fix(mcp): refresh tools after reconnect

* fix(mcp): close dynamic catalog cache gaps

* test(mcp): update OAuth connection mocks

* fix(mcp): preserve app snapshot ownership

* style(mcp): sort connection imports

* fix(mcp): close review race conditions

* fix(mcp): preserve cache ownership edges

* fix(mcp): harden recovery lifecycle

* fix(mcp): guard tool-less app refresh

* fix(mcp): fence distributed cache races

* fix(mcp): retire stale connection state

* fix(mcp): keep tool snapshots authoritative

* fix(mcp): fence stale app tool publications

* style(mcp): sort repository test imports

* test(mcp): mock empty startup publication

* fix(mcp): preserve app publication generations

* fix(mcp): harden publication recovery races

* fix(mcp): address tool catalogs by runtime config

* fix(mcp): load scoped catalogs for assistant writes

* fix(mcp): harden catalog publication recovery

* fix(mcp): serialize forced connection replacement

* fix(mcp): serialize ordinary creation with replacements

* fix(mcp): harden catalog fallback boundaries

* fix(mcp): close lifecycle fencing gaps

* fix(mcp): preserve catalog authority on failures

* fix(mcp): compensate failed catalog mutations

* fix(mcp): fence catalog refresh ordering

* style(mcp): sort agent loader imports

* fix(mcp): cancel stale connection creation

* fix(mcp): fence catalog coordination

* fix(mcp): close catalog race windows

* fix(mcp): harden cross-pod catalog fencing

* fix(mcp): close catalog lifecycle edges

* style(mcp): sort assistant imports

* fix(mcp): reject stale recovery authority

* fix(mcp): restore static catalog on every startup

* fix(mcp): order app catalog publications

* style(mcp): sort catalog revision imports

* fix(mcp): separate catalog allocation and commit fences

---------

Co-authored-by: Pascal Garber <pascal@artandcode.studio>
This commit is contained in:
Danny Avila 2026-08-08 13:50:21 -04:00 committed by GitHub
parent ef38f362ec
commit 1bccc2bc18
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 8046 additions and 831 deletions

View file

@ -164,6 +164,116 @@ jobs:
retention-days: 7
if-no-files-found: ignore
mcp_tool_list_changed:
name: MCP list_changed (replica count ${{ matrix.replicas }})
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
github.event.pull_request != null &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association))
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
replicas: [1, 2]
env:
CI: 'true'
E2E_CHROMIUM_CHANNEL: chrome
E2E_MCP_LIST_CHANGED: 'true'
E2E_REPLICAS: ${{ matrix.replicas }}
steps:
- uses: actions/checkout@v4
- name: Use Node.js 24.16.0
uses: actions/setup-node@v4
with:
node-version: '24.16.0'
- name: Restore node_modules cache
id: cache-node-modules
uses: actions/cache@v4
with:
path: |
node_modules
client/node_modules
packages/client/node_modules
packages/data-provider/node_modules
packages/data-schemas/node_modules
packages/api/node_modules
api/node_modules
key: node-modules-e2e-${{ runner.os }}-24.16.0-${{ hashFiles('package-lock.json') }}
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci
- name: Build e2e dependencies
run: npm run e2e:prepare
- name: Install Playwright and Redis runtime dependencies
timeout-minutes: 5
run: |
google-chrome --version
npx playwright install-deps chrome
sudo apt-get update
sudo apt-get install -y redis-server redis-tools
- name: Start standalone Redis and Redis Cluster
run: |
redis-server --daemonize yes --port 6379
redis-cli -p 6379 ping
chmod +x redis-config/start-cluster.sh redis-config/stop-cluster.sh
./redis-config/start-cluster.sh
redis-cli -p 7001 cluster info
- name: Test MCP notifications with in-memory cache
env:
E2E_STREAM_STORE: memory
run: >-
npx playwright test --config=e2e/playwright.config.mock.ts
mcp-tool-list-changed.spec.ts --retries=0
- name: Test MCP notifications with standalone Redis cache
env:
E2E_STREAM_STORE: redis
REDIS_URI: redis://127.0.0.1:6379
run: >-
npx playwright test --config=e2e/playwright.config.mock.ts
mcp-tool-list-changed.spec.ts --retries=0
- name: Test MCP notifications with Redis Cluster cache
env:
E2E_STREAM_STORE: redis-cluster
REDIS_URI: redis://127.0.0.1:7001,redis://127.0.0.1:7002,redis://127.0.0.1:7003
run: >-
npx playwright test --config=e2e/playwright.config.mock.ts
mcp-tool-list-changed.spec.ts --retries=0
- name: Upload Playwright HTML report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: playwright-mcp-list-changed-${{ matrix.replicas }}-replicas
path: e2e/playwright-report/**
retention-days: 7
if-no-files-found: ignore
- name: Upload traces & screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-mcp-list-changed-results-${{ matrix.replicas }}-replicas
path: e2e/specs/.test-results/**
retention-days: 7
if-no-files-found: ignore
- name: Stop Redis processes
if: always()
run: |
./redis-config/stop-cluster.sh || true
redis-cli -p 6379 shutdown || true
e2e:
name: e2e
if: >-
@ -172,9 +282,11 @@ jobs:
(github.event_name == 'pull_request' &&
github.event.pull_request != null &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association)))
needs: e2e_shards
needs: [e2e_shards, mcp_tool_list_changed]
runs-on: ubuntu-latest
steps:
- name: Verify every Playwright shard passed
if: needs.e2e_shards.result != 'success'
- name: Verify every Playwright job passed
if: >-
needs.e2e_shards.result != 'success' ||
needs.mcp_tool_list_changed.result != 'success'
run: exit 1