mirror of
https://github.com/mcp-use/mcp-use.git
synced 2026-09-14 18:06:01 +08:00
c72efe5a17
detect-changes gates every TypeScript job on the `typescript` path filter, which only matches `libraries/typescript/**`. A pull request that edits nothing but this workflow therefore matches no filter, so every job the workflow defines is skipped on the change that defines them. The three most recent workflow-only fixes all merged without the job they were wiring up ever executing: #2201 "actually run the inspector unit tests" typescript/inspector skipped #2214 "run the create-mcp-use-app unit tests" typescript/create-app-tests skipped #2213 "actually run the mcp-use unit tests" no TypeScript job ran Add `.github/workflows/ci.yml` to the typescript and create-mcp-use-app filters, matching what #2331 already did for knip. GitHub uses the pull request's own copy of the workflow for `pull_request` runs, so the jobs that run are the edited ones.
1105 lines
39 KiB
YAML
1105 lines
39 KiB
YAML
name: CI
|
||
permissions:
|
||
contents: read
|
||
|
||
# ============================================================================
|
||
# CI Workflow Overview
|
||
# ============================================================================
|
||
#
|
||
# This workflow provides comprehensive testing for both Python and TypeScript
|
||
# libraries with intelligent path-based filtering to only run relevant tests.
|
||
#
|
||
# WORKFLOW STRUCTURE:
|
||
#
|
||
# 1. detect-changes (Always runs first)
|
||
# - Uses path filters to determine which parts of the codebase changed
|
||
# - Outputs: python, typescript, knip, create-mcp-use-app
|
||
# - Subsequent jobs use these outputs to conditionally run
|
||
#
|
||
# 2. Python Tests (runs if libraries/python/** changed)
|
||
# ├─ python-lint: Ruff linting and formatting checks
|
||
# ├─ python-unit-tests: Matrix [Python 3.11, 3.12]
|
||
# ├─ python-transport-tests: Matrix [stdio, sse, streamable_http]
|
||
# │ └─ python-transport-tests-report: Posts PR comment with test results table
|
||
# ├─ python-primitive-tests: Matrix [7 MCP primitives]
|
||
# │ └─ python-primitive-tests-report: Posts PR comment with test results table
|
||
# ├─ python-integration-tests: Other integration tests
|
||
# └─ python-agent-tests: Matrix [4 agent test suites] (requires API keys)
|
||
#
|
||
# 3. TypeScript Tests (runs if libraries/typescript/** or this workflow changed)
|
||
# ├─ typescript-lint: ESLint checks
|
||
# ├─ typescript-build: Build all packages
|
||
# ├─ typescript-knip: Unused code/deps check (fails CI on findings)
|
||
# ├─ typescript/mcp-use: Unit tests + agent integration tests (requires API keys)
|
||
# ├─ typescript/inspector: Unit tests for inspector package
|
||
# ├─ typescript/cli: Unit tests for CLI package
|
||
# ├─ typescript/tunnel: Protocol and local relay integration tests
|
||
# ├─ typescript-release-channel-test: release-channel helper suite (main + canary PRs)
|
||
# └─ typescript-changeset-check: PR validation (PRs only)
|
||
#
|
||
# 4. create-mcp-use-app Tests (runs if that package or this workflow changed)
|
||
# ├─ create-mcp-use-app-build: Build, unit test, and pack the CLI
|
||
# └─ create-app-tests: Matrix [3 OS × 3 PM × 3 templates × 2 flags]
|
||
# - Tests project creation across Ubuntu/macOS/Windows
|
||
# - Tests npm, yarn, pnpm package managers
|
||
# - Tests starter, mcp-ui, mcp-apps templates
|
||
# - Collapsed into one expandable job for clean UI
|
||
#
|
||
|
||
# ============================================================================
|
||
|
||
on:
|
||
push:
|
||
branches: [main, canary]
|
||
pull_request:
|
||
branches: [main, canary]
|
||
workflow_call:
|
||
inputs:
|
||
ref:
|
||
description: "Commit SHA or ref to test"
|
||
required: true
|
||
type: string
|
||
python:
|
||
description: "Run Python jobs"
|
||
required: false
|
||
type: boolean
|
||
default: true
|
||
typescript:
|
||
description: "Run TypeScript jobs"
|
||
required: false
|
||
type: boolean
|
||
default: true
|
||
create_mcp_use_app:
|
||
description: "Run create-mcp-use-app jobs"
|
||
required: false
|
||
type: boolean
|
||
default: false
|
||
run_changeset_check:
|
||
description: "Run changeset check (usually PR-only)"
|
||
required: false
|
||
type: boolean
|
||
default: false
|
||
|
||
jobs:
|
||
detect-changes:
|
||
runs-on: ubuntu-latest
|
||
outputs:
|
||
python: ${{ steps.out.outputs.python }}
|
||
typescript: ${{ steps.out.outputs.typescript }}
|
||
knip: ${{ steps.out.outputs.knip }}
|
||
create-mcp-use-app: ${{ steps.out.outputs.create-mcp-use-app }}
|
||
release-channel: ${{ steps.out.outputs.release-channel }}
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
fetch-depth: 0
|
||
- uses: dorny/paths-filter@v3
|
||
id: filter
|
||
if: github.event_name != 'workflow_call'
|
||
with:
|
||
filters: |
|
||
python:
|
||
- 'libraries/python/**'
|
||
typescript:
|
||
- 'libraries/typescript/**'
|
||
- '.github/workflows/ci.yml'
|
||
knip:
|
||
- 'libraries/typescript/**'
|
||
- '.github/workflows/ci.yml'
|
||
create-mcp-use-app:
|
||
- 'libraries/typescript/packages/create-mcp-use-app/**'
|
||
- '.github/workflows/ci.yml'
|
||
release-channel:
|
||
- 'libraries/typescript/scripts/release-channel.mjs'
|
||
- 'libraries/typescript/scripts/release-channel.test.mjs'
|
||
- 'libraries/typescript/package.json'
|
||
- '.github/workflows/ci.yml'
|
||
- '.github/workflows/typescript-release.yml'
|
||
- name: Compute outputs
|
||
id: out
|
||
uses: actions/github-script@v7
|
||
env:
|
||
CI_INPUT_PYTHON: ${{ inputs.python }}
|
||
CI_INPUT_TYPESCRIPT: ${{ inputs.typescript }}
|
||
CI_INPUT_CREATE: ${{ inputs.create_mcp_use_app }}
|
||
with:
|
||
script: |
|
||
const fromWfCall = context.eventName === 'workflow_call';
|
||
const py = fromWfCall ? (process.env.CI_INPUT_PYTHON === 'true') : ("${{ steps.filter.outputs.python }}" === 'true');
|
||
const ts = fromWfCall ? (process.env.CI_INPUT_TYPESCRIPT === 'true') : ("${{ steps.filter.outputs.typescript }}" === 'true');
|
||
const knip = fromWfCall ? (process.env.CI_INPUT_TYPESCRIPT === 'true') : ("${{ steps.filter.outputs.knip }}" === 'true');
|
||
const create = fromWfCall ? (process.env.CI_INPUT_CREATE === 'true') : ("${{ steps.filter.outputs.create-mcp-use-app }}" === 'true');
|
||
core.setOutput('python', String(py));
|
||
core.setOutput('typescript', String(ts));
|
||
core.setOutput('knip', String(knip));
|
||
core.setOutput('create-mcp-use-app', String(create));
|
||
// The reusable-workflow caller only reports python/typescript/create-app,
|
||
// so it cannot tell us whether a release workflow changed. The helper test
|
||
// takes a few seconds, so run it on every workflow_call rather than guess.
|
||
const releaseChannel = fromWfCall ? true : ("${{ steps.filter.outputs.release-channel }}" === 'true');
|
||
core.setOutput('release-channel', String(releaseChannel));
|
||
|
||
# Python Jobs
|
||
python-lint:
|
||
needs: detect-changes
|
||
if: needs.detect-changes.outputs.python == 'true'
|
||
runs-on: ubuntu-latest
|
||
defaults:
|
||
run:
|
||
working-directory: libraries/python
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
- name: Set up Python 3.11
|
||
uses: actions/setup-python@v4
|
||
with:
|
||
python-version: "3.11"
|
||
- name: Install dependencies
|
||
run: |
|
||
python -m pip install --upgrade pip
|
||
# Pinned so a new ruff release cannot change what this job checks.
|
||
# 0.16.0 began formatting fenced code blocks inside Markdown, which
|
||
# turned every libraries/python PR red on README examples that were
|
||
# never covered before. Bump deliberately, alongside any reformat.
|
||
pip install ruff==0.15.22
|
||
- name: Lint with ruff
|
||
run: |
|
||
ruff check .
|
||
- name: Format check with ruff
|
||
run: |
|
||
ruff format --check .
|
||
|
||
# (skipped) python-ty-check job is disabled, pre commits will make use compliant, then we can add this back.
|
||
# Pietro: this is me, I will check when is a good time to re enable. keep the comment.
|
||
# python-ty-check:
|
||
# needs: python-lint
|
||
# runs-on: ubuntu-latest
|
||
# defaults:
|
||
# run:
|
||
# working-directory: libraries/python
|
||
# steps:
|
||
# - uses: actions/checkout@v4
|
||
# with:
|
||
# ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
# - name: Set up Python 3.11
|
||
# uses: actions/setup-python@v4
|
||
# with:
|
||
# python-version: "3.11"
|
||
# - name: Install dependencies
|
||
# run: |
|
||
# python -m pip install --upgrade pip
|
||
# pip install ty
|
||
# - name: Type check with ty
|
||
# run: |
|
||
# ty check .
|
||
|
||
python-unit-tests:
|
||
needs: python-lint
|
||
name: "python-unit"
|
||
runs-on: ubuntu-latest
|
||
strategy:
|
||
matrix:
|
||
python-version: ["3.11", "3.12"]
|
||
deps: ["latest", "minimum"]
|
||
defaults:
|
||
run:
|
||
working-directory: libraries/python
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
- name: Set up Python ${{ matrix.python-version }}
|
||
uses: actions/setup-python@v4
|
||
with:
|
||
python-version: ${{ matrix.python-version }}
|
||
- name: Install uv
|
||
run: pip install uv
|
||
- name: Install dependencies (latest)
|
||
if: matrix.deps == 'latest'
|
||
run: uv pip install --system -e ".[dev,anthropic,openai,search,e2b]"
|
||
- name: Install dependencies (minimum)
|
||
if: matrix.deps == 'minimum'
|
||
run: uv pip install --system -e ".[dev,anthropic,openai,search,e2b]" --resolution lowest-direct
|
||
- name: Test with pytest
|
||
env:
|
||
MCP_USE_ANONYMIZED_TELEMETRY: false
|
||
run: pytest -vv tests/unit
|
||
|
||
python-transport-tests:
|
||
needs: python-lint
|
||
name: "python-transport/${{ matrix.transport }}"
|
||
runs-on: ubuntu-latest
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
transport: [stdio, sse, streamable_http]
|
||
defaults:
|
||
run:
|
||
working-directory: libraries/python
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
- name: Set up Python 3.11
|
||
uses: actions/setup-python@v4
|
||
with:
|
||
python-version: "3.11"
|
||
- name: Install uv
|
||
run: |
|
||
pip install uv
|
||
- name: Install dependencies
|
||
run: |
|
||
uv pip install --system .[dev,anthropic,openai,search,e2b]
|
||
- name: Run integration tests for ${{ matrix.transport }} transport
|
||
id: test
|
||
env:
|
||
MCP_USE_ANONYMIZED_TELEMETRY: false
|
||
run: |
|
||
pytest -vv tests/integration/client/transports/test_${{ matrix.transport }}.py \
|
||
--junit-xml=test-results-${{ matrix.transport }}.xml || true
|
||
# Check if tests passed
|
||
if grep -q 'failures="0"' test-results-${{ matrix.transport }}.xml && \
|
||
grep -q 'errors="0"' test-results-${{ matrix.transport }}.xml; then
|
||
echo "status=passed" >> $GITHUB_OUTPUT
|
||
else
|
||
echo "status=failed" >> $GITHUB_OUTPUT
|
||
fi
|
||
- name: Upload test results
|
||
uses: actions/upload-artifact@v4
|
||
if: always()
|
||
with:
|
||
name: transport-test-results-${{ matrix.transport }}
|
||
path: libraries/python/test-results-${{ matrix.transport }}.xml
|
||
retention-days: 7
|
||
- name: Fail if tests failed
|
||
if: steps.test.outputs.status == 'failed'
|
||
run: exit 1
|
||
|
||
python-transport-tests-report:
|
||
needs: python-transport-tests
|
||
if: always() && github.event_name == 'pull_request' && needs.python-transport-tests.result != 'skipped'
|
||
runs-on: ubuntu-latest
|
||
permissions:
|
||
pull-requests: write
|
||
steps:
|
||
- name: Download all test results
|
||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||
with:
|
||
pattern: transport-test-results-*
|
||
path: test-results
|
||
merge-multiple: false
|
||
- name: Generate PR comment
|
||
uses: actions/github-script@v7
|
||
with:
|
||
script: |
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const transports = ['stdio', 'sse', 'streamable_http'];
|
||
let tableRows = [];
|
||
let allPassed = true;
|
||
|
||
for (const transport of transports) {
|
||
const xmlPath = path.join('test-results', `transport-test-results-${transport}`, `test-results-${transport}.xml`);
|
||
let status = '⏭️ Skipped';
|
||
let tests = '-';
|
||
let passed = '-';
|
||
let failed = '-';
|
||
let errors = '-';
|
||
let time = '-';
|
||
|
||
if (fs.existsSync(xmlPath)) {
|
||
const content = fs.readFileSync(xmlPath, 'utf8');
|
||
|
||
// Parse basic stats from JUnit XML
|
||
const testsMatch = content.match(/tests="(\d+)"/);
|
||
const failuresMatch = content.match(/failures="(\d+)"/);
|
||
const errorsMatch = content.match(/errors="(\d+)"/);
|
||
const timeMatch = content.match(/time="([\d.]+)"/);
|
||
|
||
if (testsMatch) {
|
||
const totalTests = parseInt(testsMatch[1]);
|
||
const failureCount = failuresMatch ? parseInt(failuresMatch[1]) : 0;
|
||
const errorCount = errorsMatch ? parseInt(errorsMatch[1]) : 0;
|
||
const passedCount = totalTests - failureCount - errorCount;
|
||
|
||
tests = totalTests.toString();
|
||
passed = passedCount.toString();
|
||
failed = failureCount.toString();
|
||
errors = errorCount.toString();
|
||
time = timeMatch ? `${parseFloat(timeMatch[1]).toFixed(2)}s` : '-';
|
||
|
||
if (failureCount === 0 && errorCount === 0) {
|
||
status = '✅ Passed';
|
||
} else {
|
||
status = '❌ Failed';
|
||
allPassed = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
tableRows.push(`| ${transport} | ${status} | ${tests} | ${passed} | ${failed} | ${errors} | ${time} |`);
|
||
}
|
||
|
||
const overallStatus = allPassed ? '✅ All transport tests passed!' : '❌ Some transport tests failed';
|
||
|
||
const comment = `## Python Transport Tests Results
|
||
|
||
${overallStatus}
|
||
|
||
| Transport | Status | Tests | Passed | Failed | Errors | Time |
|
||
|-----------|--------|-------|--------|--------|--------|------|
|
||
${tableRows.join('\n')}
|
||
`;
|
||
|
||
// Find existing comment
|
||
const { data: comments } = await github.rest.issues.listComments({
|
||
owner: context.repo.owner,
|
||
repo: context.repo.repo,
|
||
issue_number: context.issue.number,
|
||
});
|
||
|
||
const botComment = comments.find(c =>
|
||
c.user.type === 'Bot' &&
|
||
c.body.includes('## Python Transport Tests Results')
|
||
);
|
||
|
||
if (botComment) {
|
||
await github.rest.issues.updateComment({
|
||
owner: context.repo.owner,
|
||
repo: context.repo.repo,
|
||
comment_id: botComment.id,
|
||
body: comment,
|
||
});
|
||
} else {
|
||
await github.rest.issues.createComment({
|
||
owner: context.repo.owner,
|
||
repo: context.repo.repo,
|
||
issue_number: context.issue.number,
|
||
body: comment,
|
||
});
|
||
}
|
||
|
||
python-primitive-tests:
|
||
needs: python-lint
|
||
name: "python-primitive/${{ matrix.primitive }}"
|
||
runs-on: ubuntu-latest
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
primitive:
|
||
[
|
||
sampling,
|
||
tools,
|
||
resources,
|
||
prompts,
|
||
elicitation,
|
||
notifications,
|
||
auth,
|
||
roots,
|
||
]
|
||
defaults:
|
||
run:
|
||
working-directory: libraries/python
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
- name: Set up Python 3.11
|
||
uses: actions/setup-python@v4
|
||
with:
|
||
python-version: "3.11"
|
||
- name: Install uv
|
||
run: |
|
||
pip install uv
|
||
- name: Install dependencies
|
||
run: |
|
||
uv pip install --system .[dev,anthropic,openai,search,e2b]
|
||
- name: Run integration tests for ${{ matrix.primitive }} primitive
|
||
id: test
|
||
env:
|
||
MCP_USE_ANONYMIZED_TELEMETRY: false
|
||
run: |
|
||
pytest -vv tests/integration/client/primitives/test_${{ matrix.primitive }}.py \
|
||
--junit-xml=test-results-${{ matrix.primitive }}.xml || true
|
||
# Check if tests passed
|
||
if grep -q 'failures="0"' test-results-${{ matrix.primitive }}.xml && \
|
||
grep -q 'errors="0"' test-results-${{ matrix.primitive }}.xml; then
|
||
echo "status=passed" >> $GITHUB_OUTPUT
|
||
else
|
||
echo "status=failed" >> $GITHUB_OUTPUT
|
||
fi
|
||
- name: Upload test results
|
||
uses: actions/upload-artifact@v4
|
||
if: always()
|
||
with:
|
||
name: primitive-test-results-${{ matrix.primitive }}
|
||
path: libraries/python/test-results-${{ matrix.primitive }}.xml
|
||
retention-days: 7
|
||
- name: Fail if tests failed
|
||
if: steps.test.outputs.status == 'failed'
|
||
run: exit 1
|
||
|
||
python-primitive-tests-report:
|
||
needs: python-primitive-tests
|
||
if: always() && github.event_name == 'pull_request' && needs.python-primitive-tests.result != 'skipped'
|
||
runs-on: ubuntu-latest
|
||
permissions:
|
||
pull-requests: write
|
||
steps:
|
||
- name: Download all test results
|
||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||
with:
|
||
pattern: primitive-test-results-*
|
||
path: test-results
|
||
merge-multiple: false
|
||
- name: Generate PR comment
|
||
uses: actions/github-script@v7
|
||
with:
|
||
script: |
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const primitives = ['sampling', 'tools', 'resources', 'prompts', 'elicitation', 'notifications', 'auth', 'roots'];
|
||
let tableRows = [];
|
||
let allPassed = true;
|
||
|
||
for (const primitive of primitives) {
|
||
const xmlPath = path.join('test-results', `primitive-test-results-${primitive}`, `test-results-${primitive}.xml`);
|
||
let status = '⏭️ Skipped';
|
||
let tests = '-';
|
||
let passed = '-';
|
||
let failed = '-';
|
||
let errors = '-';
|
||
let time = '-';
|
||
|
||
if (fs.existsSync(xmlPath)) {
|
||
const content = fs.readFileSync(xmlPath, 'utf8');
|
||
|
||
// Parse basic stats from JUnit XML
|
||
const testsMatch = content.match(/tests="(\d+)"/);
|
||
const failuresMatch = content.match(/failures="(\d+)"/);
|
||
const errorsMatch = content.match(/errors="(\d+)"/);
|
||
const timeMatch = content.match(/time="([\d.]+)"/);
|
||
|
||
if (testsMatch) {
|
||
const totalTests = parseInt(testsMatch[1]);
|
||
const failureCount = failuresMatch ? parseInt(failuresMatch[1]) : 0;
|
||
const errorCount = errorsMatch ? parseInt(errorsMatch[1]) : 0;
|
||
const passedCount = totalTests - failureCount - errorCount;
|
||
|
||
tests = totalTests.toString();
|
||
passed = passedCount.toString();
|
||
failed = failureCount.toString();
|
||
errors = errorCount.toString();
|
||
time = timeMatch ? `${parseFloat(timeMatch[1]).toFixed(2)}s` : '-';
|
||
|
||
if (failureCount === 0 && errorCount === 0) {
|
||
status = '✅ Passed';
|
||
} else {
|
||
status = '❌ Failed';
|
||
allPassed = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
tableRows.push(`| ${primitive} | ${status} | ${tests} | ${passed} | ${failed} | ${errors} | ${time} |`);
|
||
}
|
||
|
||
const overallStatus = allPassed ? '✅ All primitive tests passed!' : '❌ Some primitive tests failed';
|
||
|
||
const comment = `## Python Primitive Tests Results
|
||
|
||
${overallStatus}
|
||
|
||
| Primitive | Status | Tests | Passed | Failed | Errors | Time |
|
||
|-----------|--------|-------|--------|--------|--------|------|
|
||
${tableRows.join('\n')}
|
||
`;
|
||
|
||
// Find existing comment
|
||
const { data: comments } = await github.rest.issues.listComments({
|
||
owner: context.repo.owner,
|
||
repo: context.repo.repo,
|
||
issue_number: context.issue.number,
|
||
});
|
||
|
||
const botComment = comments.find(c =>
|
||
c.user.type === 'Bot' &&
|
||
c.body.includes('## Python Primitive Tests Results')
|
||
);
|
||
|
||
if (botComment) {
|
||
await github.rest.issues.updateComment({
|
||
owner: context.repo.owner,
|
||
repo: context.repo.repo,
|
||
comment_id: botComment.id,
|
||
body: comment,
|
||
});
|
||
} else {
|
||
await github.rest.issues.createComment({
|
||
owner: context.repo.owner,
|
||
repo: context.repo.repo,
|
||
issue_number: context.issue.number,
|
||
body: comment,
|
||
});
|
||
}
|
||
|
||
python-integration-tests:
|
||
needs: python-lint
|
||
name: "python-integration"
|
||
runs-on: ubuntu-latest
|
||
defaults:
|
||
run:
|
||
working-directory: libraries/python
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
- name: Set up Python 3.11
|
||
uses: actions/setup-python@v4
|
||
with:
|
||
python-version: "3.11"
|
||
- name: Install uv
|
||
run: |
|
||
pip install uv
|
||
- name: Install dependencies
|
||
run: |
|
||
uv pip install --system .[dev,anthropic,openai,search,e2b]
|
||
- name: Run other integration tests
|
||
env:
|
||
MCP_USE_ANONYMIZED_TELEMETRY: false
|
||
run: pytest -vv tests/integration/client/others/
|
||
|
||
python-agent-tests:
|
||
needs: python-lint
|
||
name: "python-agent/${{ matrix.test }}"
|
||
runs-on: ubuntu-latest
|
||
env:
|
||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
test: [agent_run, agent_stream, agent_structured_output, server_manager]
|
||
defaults:
|
||
run:
|
||
working-directory: libraries/python
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
- name: Set up Python 3.11
|
||
uses: actions/setup-python@v4
|
||
with:
|
||
python-version: "3.11"
|
||
- name: Install uv
|
||
run: |
|
||
pip install uv
|
||
- name: Install dependencies
|
||
run: |
|
||
uv pip install --system .[dev,anthropic,openai,search,e2b]
|
||
- name: Run agent test for ${{ matrix.test }}
|
||
if: env.OPENAI_API_KEY != ''
|
||
env:
|
||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||
MCP_USE_ANONYMIZED_TELEMETRY: false
|
||
run: pytest -vv tests/integration/agent/test_${{ matrix.test }}.py
|
||
|
||
# TypeScript Jobs
|
||
typescript-lint:
|
||
needs: detect-changes
|
||
if: needs.detect-changes.outputs.typescript == 'true'
|
||
runs-on: ubuntu-latest
|
||
strategy:
|
||
matrix:
|
||
node-version: [22.23.1]
|
||
name: typescript-lint (node-${{ matrix.node-version }})
|
||
defaults:
|
||
run:
|
||
working-directory: libraries/typescript
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
|
||
- name: Setup pnpm
|
||
uses: pnpm/action-setup@v4
|
||
with:
|
||
version: 11.13.1
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version: ${{ matrix.node-version }}
|
||
cache: "pnpm"
|
||
cache-dependency-path: libraries/typescript/pnpm-lock.yaml
|
||
|
||
- name: Install Dependencies
|
||
run: pnpm install --no-frozen-lockfile
|
||
|
||
- name: Build Client Types
|
||
run: pnpm build:client
|
||
|
||
- name: Build Tunnel Types
|
||
run: pnpm build:tunnel
|
||
|
||
- name: Check Formatting
|
||
run: pnpm format:check
|
||
|
||
- name: Run Linter
|
||
run: pnpm lint
|
||
|
||
typescript-build:
|
||
needs: typescript-lint
|
||
runs-on: ubuntu-latest
|
||
strategy:
|
||
matrix:
|
||
node-version: [22.23.1]
|
||
name: typescript-build (node-${{ matrix.node-version }})
|
||
defaults:
|
||
run:
|
||
working-directory: libraries/typescript
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
|
||
- name: Setup pnpm
|
||
uses: pnpm/action-setup@v4
|
||
with:
|
||
version: 11.13.1
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version: ${{ matrix.node-version }}
|
||
cache: "pnpm"
|
||
cache-dependency-path: libraries/typescript/pnpm-lock.yaml
|
||
|
||
- name: Install Dependencies
|
||
run: pnpm install --no-frozen-lockfile
|
||
|
||
- name: Build Packages
|
||
run: pnpm build
|
||
|
||
typescript-test-mcp-use:
|
||
needs: typescript-lint
|
||
name: "typescript/mcp-use (node-${{ matrix.node-version }})"
|
||
runs-on: ubuntu-latest
|
||
env:
|
||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||
strategy:
|
||
matrix:
|
||
node-version: [22.23.1]
|
||
defaults:
|
||
run:
|
||
working-directory: libraries/typescript
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
- uses: pnpm/action-setup@v4
|
||
with:
|
||
version: 11.13.1
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: ${{ matrix.node-version }}
|
||
cache: "pnpm"
|
||
cache-dependency-path: libraries/typescript/pnpm-lock.yaml
|
||
- name: Install Dependencies
|
||
run: pnpm install --no-frozen-lockfile
|
||
- name: Build Packages
|
||
run: pnpm build
|
||
- name: Run client Unit Tests
|
||
env:
|
||
MCP_USE_ANONYMIZED_TELEMETRY: false
|
||
run: pnpm --filter @mcp-use/client test:run
|
||
- name: Run mcp-use Unit Tests
|
||
env:
|
||
MCP_USE_ANONYMIZED_TELEMETRY: false
|
||
run: pnpm --filter mcp-use test:run
|
||
- name: Run @mcp-use/agent Unit Tests
|
||
env:
|
||
MCP_USE_ANONYMIZED_TELEMETRY: false
|
||
run: pnpm --filter @mcp-use/agent test:unit
|
||
- name: Run @mcp-use/agent native OpenAI e2e
|
||
if: env.OPENAI_API_KEY != ''
|
||
env:
|
||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||
MCP_USE_ANONYMIZED_TELEMETRY: false
|
||
run: pnpm --filter @mcp-use/agent test:integration:native-openai
|
||
|
||
typescript-knip:
|
||
needs: detect-changes
|
||
if: needs.detect-changes.outputs.knip == 'true'
|
||
name: typescript-knip
|
||
runs-on: ubuntu-latest
|
||
defaults:
|
||
run:
|
||
working-directory: libraries/typescript
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
- uses: pnpm/action-setup@v4
|
||
with:
|
||
version: 11.13.1
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: 22.23.1
|
||
cache: "pnpm"
|
||
cache-dependency-path: libraries/typescript/pnpm-lock.yaml
|
||
- name: Install Dependencies
|
||
run: pnpm install --frozen-lockfile
|
||
- name: Run Knip
|
||
run: pnpm exec knip --reporter github-actions
|
||
|
||
typescript-test-inspector:
|
||
needs: typescript-lint
|
||
name: "typescript/inspector (node-${{ matrix.node-version }})"
|
||
runs-on: ubuntu-latest
|
||
strategy:
|
||
matrix:
|
||
node-version: [22.23.1]
|
||
defaults:
|
||
run:
|
||
working-directory: libraries/typescript
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
- uses: pnpm/action-setup@v4
|
||
with:
|
||
version: 11.13.1
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: ${{ matrix.node-version }}
|
||
cache: "pnpm"
|
||
cache-dependency-path: libraries/typescript/pnpm-lock.yaml
|
||
- name: Install Dependencies
|
||
run: pnpm install --no-frozen-lockfile
|
||
- name: Build Packages
|
||
run: pnpm build
|
||
- name: Run Inspector Tests
|
||
env:
|
||
MCP_USE_ANONYMIZED_TELEMETRY: false
|
||
run: pnpm --filter @mcp-use/inspector test:unit
|
||
|
||
typescript-test-cli:
|
||
needs: typescript-lint
|
||
name: "typescript/cli (${{ matrix.os }}, node-${{ matrix.node-version }})"
|
||
runs-on: ${{ matrix.os }}
|
||
strategy:
|
||
# Keep the other OS running when one fails: comparing the two is the
|
||
# whole reason this job has an os matrix.
|
||
fail-fast: false
|
||
matrix:
|
||
os: [ubuntu-latest, windows-latest]
|
||
node-version: [22.23.1]
|
||
defaults:
|
||
run:
|
||
working-directory: libraries/typescript
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
- uses: pnpm/action-setup@v4
|
||
with:
|
||
version: 11.13.1
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: ${{ matrix.node-version }}
|
||
cache: "pnpm"
|
||
cache-dependency-path: libraries/typescript/pnpm-lock.yaml
|
||
- name: Install Dependencies
|
||
run: pnpm install --no-frozen-lockfile
|
||
- name: Build Packages
|
||
run: pnpm build
|
||
- name: Verify CLI build artifact
|
||
shell: bash
|
||
run: test -f packages/cli/dist/bin.js || { echo "::warning::dist/bin.js missing after pnpm build, rebuilding CLI explicitly"; pnpm --filter @mcp-use/cli build; }
|
||
- name: Run CLI Tests
|
||
env:
|
||
MCP_USE_ANONYMIZED_TELEMETRY: false
|
||
run: pnpm --filter @mcp-use/cli --if-present test:run
|
||
|
||
typescript-test-tunnel:
|
||
needs: typescript-lint
|
||
name: "typescript/tunnel (node-${{ matrix.node-version }})"
|
||
runs-on: ubuntu-latest
|
||
strategy:
|
||
matrix:
|
||
node-version: [22.23.1]
|
||
defaults:
|
||
run:
|
||
working-directory: libraries/typescript
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: pnpm/action-setup@v4
|
||
with:
|
||
version: 11.13.1
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: ${{ matrix.node-version }}
|
||
cache: pnpm
|
||
cache-dependency-path: libraries/typescript/pnpm-lock.yaml
|
||
- name: Install Dependencies
|
||
run: pnpm install --frozen-lockfile
|
||
- name: Build Tunnel
|
||
run: pnpm build:tunnel
|
||
- name: Run Tunnel Tests
|
||
run: pnpm --filter @mcp-use/tunnel test:run
|
||
|
||
typescript-release-channel-test:
|
||
needs: detect-changes
|
||
if: needs.detect-changes.outputs.release-channel == 'true'
|
||
runs-on: ubuntu-latest
|
||
defaults:
|
||
run:
|
||
working-directory: libraries/typescript
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
|
||
- name: Setup pnpm
|
||
uses: pnpm/action-setup@v4
|
||
with:
|
||
version: 11.13.1
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version: 22.23.1
|
||
cache: "pnpm"
|
||
cache-dependency-path: libraries/typescript/pnpm-lock.yaml
|
||
|
||
- name: Install Dependencies
|
||
run: pnpm install --no-frozen-lockfile
|
||
|
||
- name: Test release-channel helper
|
||
run: pnpm test:release-channel
|
||
|
||
typescript-changeset-check:
|
||
needs: detect-changes
|
||
if: ${{ (github.event_name == 'workflow_call' && inputs.run_changeset_check) || (github.event_name == 'pull_request' && github.base_ref != 'canary' && needs.detect-changes.outputs.typescript == 'true') }}
|
||
runs-on: ubuntu-latest
|
||
defaults:
|
||
run:
|
||
working-directory: libraries/typescript
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
fetch-depth: 0
|
||
|
||
- name: Setup pnpm
|
||
uses: pnpm/action-setup@v4
|
||
with:
|
||
version: 11.13.1
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version: 22.23.1
|
||
cache: "pnpm"
|
||
cache-dependency-path: libraries/typescript/pnpm-lock.yaml
|
||
|
||
- name: Install Dependencies
|
||
run: pnpm install --no-frozen-lockfile
|
||
|
||
- name: Prevent canary-to-main version regression
|
||
if: github.event_name == 'pull_request' && github.base_ref == 'main' && github.head_ref == 'canary'
|
||
run: pnpm release-channel preflight --channel stable
|
||
|
||
- name: Check for Changesets
|
||
env:
|
||
BASE_REF: ${{ github.base_ref || 'main' }}
|
||
HEAD_REF: ${{ github.head_ref }}
|
||
run: |
|
||
if [ "$BASE_REF" = "main" ] && [ "$HEAD_REF" = "canary" ]; then
|
||
pending_changesets="$(node scripts/release-channel.mjs pending)"
|
||
if [ -n "$pending_changesets" ]; then
|
||
echo "✅ Pending changeset found"
|
||
printf '%s\n' "$pending_changesets"
|
||
pnpm changeset status --since="origin/${BASE_REF}"
|
||
else
|
||
echo "✅ No pending changesets; prerelease changesets are already applied"
|
||
fi
|
||
elif [ -n "$(ls -A .changeset/*.md 2>/dev/null | grep -v README.md)" ]; then
|
||
echo "✅ Changeset found"
|
||
pnpm changeset status --since="origin/${BASE_REF}"
|
||
else
|
||
echo "⚠️ No changeset found. If this PR includes changes that should be published, please add a changeset:"
|
||
echo " pnpm changeset"
|
||
echo ""
|
||
echo "If this PR doesn't require a changeset (docs, tests, internal changes), you can ignore this message."
|
||
fi
|
||
|
||
# create-mcp-use-app Tests (only when CLI package changes)
|
||
create-mcp-use-app-build:
|
||
needs: [detect-changes, typescript-lint]
|
||
if: needs.detect-changes.outputs.create-mcp-use-app == 'true'
|
||
name: "typescript/create-mcp-use-app/build"
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
- uses: pnpm/action-setup@v4
|
||
with:
|
||
version: 11.13.1
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: 22.23.1
|
||
cache: "pnpm"
|
||
cache-dependency-path: libraries/typescript/pnpm-lock.yaml
|
||
- name: Install Dependencies
|
||
working-directory: libraries/typescript
|
||
run: pnpm install --no-frozen-lockfile
|
||
- name: Build All Packages
|
||
working-directory: libraries/typescript
|
||
run: pnpm build
|
||
- name: Run create-mcp-use-app Unit Tests
|
||
working-directory: libraries/typescript
|
||
run: pnpm --filter create-mcp-use-app test
|
||
- name: Pack create-mcp-use-app
|
||
working-directory: libraries/typescript/packages/create-mcp-use-app
|
||
run: npm pack
|
||
- name: Pack @mcp-use/cli
|
||
working-directory: libraries/typescript/packages/cli
|
||
run: npm pack
|
||
- name: Pack @mcp-use/inspector
|
||
working-directory: libraries/typescript/packages/inspector
|
||
run: npm pack
|
||
- name: Pack mcp-use
|
||
working-directory: libraries/typescript/packages/server
|
||
run: npm pack
|
||
- name: Upload All Package Artifacts
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: workspace-packages
|
||
path: |
|
||
libraries/typescript/packages/create-mcp-use-app/create-mcp-use-app-*.tgz
|
||
libraries/typescript/packages/server/mcp-use-*.tgz
|
||
libraries/typescript/packages/cli/mcp-use-cli-*.tgz
|
||
libraries/typescript/packages/inspector/mcp-use-inspector-*.tgz
|
||
retention-days: 1
|
||
|
||
create-mcp-use-app-tests:
|
||
needs: create-mcp-use-app-build
|
||
name: "typescript/create-app-tests"
|
||
runs-on: ${{ matrix.os }}
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||
pm: [npm, yarn, pnpm]
|
||
template: [starter, mcp-apps, blank]
|
||
flag: ["", "--dev"]
|
||
exclude:
|
||
# Only test --dev on ubuntu with npm
|
||
- os: macos-latest
|
||
flag: "--dev"
|
||
- os: windows-latest
|
||
flag: "--dev"
|
||
- pm: yarn
|
||
flag: "--dev"
|
||
- pm: pnpm
|
||
flag: "--dev"
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.sha }}
|
||
- uses: pnpm/action-setup@v4
|
||
if: matrix.pm == 'pnpm'
|
||
with:
|
||
version: 11.13.1
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: 22.23.1
|
||
- name: Setup Yarn
|
||
if: matrix.pm == 'yarn'
|
||
run: corepack enable
|
||
- name: Download Package Artifact
|
||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||
with:
|
||
name: workspace-packages
|
||
path: ./packages
|
||
- name: Test CLI Creation
|
||
shell: bash
|
||
run: |
|
||
# Get package path
|
||
if [ "${{ runner.os }}" == "Windows" ]; then
|
||
PACKAGE_PATH=$(powershell -Command "(Get-ChildItem -Recurse -Filter 'create-mcp-use-app-*.tgz' | Select-Object -First 1).FullName")
|
||
else
|
||
PACKAGE_PATH=$(realpath $(find ./packages -name 'create-mcp-use-app-*.tgz' -type f | head -n1))
|
||
fi
|
||
|
||
# Run create command based on PM
|
||
case "${{ matrix.pm }}" in
|
||
npm)
|
||
npx --yes --package="$PACKAGE_PATH" create-mcp-use-app test-app --template ${{ matrix.template }} ${{ matrix.flag }}
|
||
;;
|
||
yarn)
|
||
yarn dlx -p "create-mcp-use-app@file:$PACKAGE_PATH" create-mcp-use-app test-app --template ${{ matrix.template }} ${{ matrix.flag }}
|
||
;;
|
||
pnpm)
|
||
pnpm --package="$PACKAGE_PATH" dlx create-mcp-use-app test-app --template ${{ matrix.template }} ${{ matrix.flag }}
|
||
;;
|
||
esac
|
||
|
||
# Verify creation
|
||
[ -d "test-app" ] || exit 1
|
||
[ -f "test-app/package.json" ] || exit 1
|
||
[ -f "test-app/index.ts" ] || exit 1
|
||
echo "✅ Project created successfully"
|
||
|
||
- name: Test dev command (non --dev flag only)
|
||
if: matrix.flag == ''
|
||
shell: bash
|
||
working-directory: test-app
|
||
run: |
|
||
# Start dev server in background
|
||
case "${{ matrix.pm }}" in
|
||
npm)
|
||
npm run dev &
|
||
;;
|
||
yarn)
|
||
yarn dev &
|
||
;;
|
||
pnpm)
|
||
pnpm dev &
|
||
;;
|
||
esac
|
||
|
||
DEV_PID=$!
|
||
|
||
# Wait for server to start (10 seconds should be enough)
|
||
sleep 10
|
||
|
||
# Kill the dev server
|
||
if [ "${{ runner.os }}" == "Windows" ]; then
|
||
# On Windows, kill by port is more reliable than DEV_PID
|
||
# because bash's $! may not map to the actual Windows process ID
|
||
DEV_PORT=3000
|
||
for pid in $(netstat -ano 2>/dev/null | grep ":$DEV_PORT " | grep "LISTENING" | awk '{print $5}' | sort -u); do
|
||
taskkill //PID $pid //F 2>/dev/null || true
|
||
done
|
||
# Also try the process tree kill as fallback
|
||
taskkill //PID $DEV_PID //T //F 2>/dev/null || true
|
||
else
|
||
kill $DEV_PID 2>/dev/null || true
|
||
fi
|
||
|
||
# Wait for process to exit
|
||
wait $DEV_PID 2>/dev/null || true
|
||
|
||
echo "✅ Dev server started and stopped successfully"
|