Files

1109 lines
38 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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@v7
with:
ref: ${{ inputs.ref || github.sha }}
fetch-depth: 0
- uses: dorny/paths-filter@v3
id: filter
# Reusable workflows keep the caller's event name; ref is required for calls.
if: ${{ !inputs.ref }}
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@v9
env:
CI_INPUT_REF: ${{ inputs.ref }}
CI_INPUT_PYTHON: ${{ inputs.python }}
CI_INPUT_TYPESCRIPT: ${{ inputs.typescript }}
CI_INPUT_CREATE: ${{ inputs.create_mcp_use_app }}
with:
script: |
const fromWfCall = Boolean(process.env.CI_INPUT_REF);
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@v7
with:
ref: ${{ inputs.ref || github.sha }}
- name: Set up Python 3.11
uses: actions/setup-python@v7
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@v7
# with:
# ref: ${{ inputs.ref || github.sha }}
# - name: Set up Python 3.11
# uses: actions/setup-python@v7
# 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@v7
with:
ref: ${{ inputs.ref || github.sha }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v7
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@v7
with:
ref: ${{ inputs.ref || github.sha }}
- name: Set up Python 3.11
uses: actions/setup-python@v7
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@v7
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@v9
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@v7
with:
ref: ${{ inputs.ref || github.sha }}
- name: Set up Python 3.11
uses: actions/setup-python@v7
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@v7
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@v9
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@v7
with:
ref: ${{ inputs.ref || github.sha }}
- name: Set up Python 3.11
uses: actions/setup-python@v7
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@v7
with:
ref: ${{ inputs.ref || github.sha }}
- name: Set up Python 3.11
uses: actions/setup-python@v7
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@v7
with:
ref: ${{ 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@v7
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@v7
with:
ref: ${{ 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@v7
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@v7
with:
ref: ${{ inputs.ref || github.sha }}
- uses: pnpm/action-setup@v4
with:
version: 11.13.1
- uses: actions/setup-node@v7
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@v7
with:
ref: ${{ inputs.ref || github.sha }}
- uses: pnpm/action-setup@v4
with:
version: 11.13.1
- uses: actions/setup-node@v7
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@v7
with:
ref: ${{ inputs.ref || github.sha }}
- uses: pnpm/action-setup@v4
with:
version: 11.13.1
- uses: actions/setup-node@v7
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@v7
with:
ref: ${{ inputs.ref || github.sha }}
- uses: pnpm/action-setup@v4
with:
version: 11.13.1
- uses: actions/setup-node@v7
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@v7
with:
ref: ${{ inputs.ref || github.sha }}
- uses: pnpm/action-setup@v4
with:
version: 11.13.1
- uses: actions/setup-node@v7
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@v7
with:
ref: ${{ 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@v7
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: ${{ (inputs.ref && inputs.run_changeset_check) || (!inputs.ref && 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@v7
with:
ref: ${{ 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@v7
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@v7
with:
ref: ${{ inputs.ref || github.sha }}
- uses: pnpm/action-setup@v4
with:
version: 11.13.1
- uses: actions/setup-node@v7
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@v7
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@v7
with:
ref: ${{ inputs.ref || github.sha }}
- uses: pnpm/action-setup@v4
if: matrix.pm == 'pnpm'
with:
version: 11.13.1
- uses: actions/setup-node@v7
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"