diff --git a/.github/workflows/ci-gates.yml b/.github/workflows/ci-gates.yml index 6f865c2..a9eebb1 100644 --- a/.github/workflows/ci-gates.yml +++ b/.github/workflows/ci-gates.yml @@ -291,6 +291,49 @@ jobs: print('✅ pyproject.toml is valid') " + # ========================================== + # Jobs whose names are required checks on `main` + # ========================================== + # `fast-tests.yml` used to report these two names on pull requests and is now + # manual-only, so branch protection waited for checks that never ran. They live + # here so the required names report again. + integration-smoke: + name: Integration Smoke Tests + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' + + - name: Install dependencies + run: | + pip install --upgrade pip + pip install -e . + pip install pytest pytest-asyncio pytest-timeout + + - name: Run MCP smoke tests + run: pytest tests/integration/test_mcp_protocol_smoke.py -v --tb=short --timeout=60 + + security-quick: + name: Quick Security Scan + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' + + - name: Bandit, fail on high severity + run: | + pip install --upgrade pip bandit + bandit -r markitdown_mcp/ --severity-level high + # ========================================== # PR Summary Comment # ========================================== @@ -298,7 +341,7 @@ jobs: name: PR Summary if: github.event_name == 'pull_request' runs-on: ubuntu-latest - needs: [quality-checks, unit-tests-coverage, mcp-contract-checks, dependency-checks] + needs: [quality-checks, unit-tests-coverage, mcp-contract-checks, dependency-checks, integration-smoke, security-quick] steps: - uses: actions/checkout@v4 @@ -322,7 +365,9 @@ jobs: if [[ "${{ needs.quality-checks.result }}" != "success" || \ "${{ needs.unit-tests-coverage.result }}" != "success" || \ "${{ needs.mcp-contract-checks.result }}" != "success" || \ - "${{ needs.dependency-checks.result }}" != "success" ]]; then + "${{ needs.dependency-checks.result }}" != "success" || \ + "${{ needs.integration-smoke.result }}" != "success" || \ + "${{ needs.security-quick.result }}" != "success" ]]; then overall_status="❌ Issues Found" fi @@ -366,7 +411,7 @@ jobs: echo "coverage=${coverage}" >> $GITHUB_OUTPUT - name: Post PR comment - uses: marocchino/sticky-pull-request-comment@v2 + uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2 with: header: ci-quality-gates recreate: true @@ -378,7 +423,7 @@ jobs: ci-gates-passed: name: All CI Gates Passed runs-on: ubuntu-latest - needs: [quality-checks, unit-tests-coverage, mcp-contract-checks, dependency-checks] + needs: [quality-checks, unit-tests-coverage, mcp-contract-checks, dependency-checks, integration-smoke, security-quick] if: always() steps: - name: Check all gates passed diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..ea90ce9 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,26 @@ +# Only use this if you need an advanced workflow. CodeQL default setup already +# covers Python, and the two cannot both be enabled: switch default setup off +# first. See docs/guides/codeql.md. +name: CodeQL + +on: + push: + branches: [main] + pull_request: + schedule: + - cron: "23 5 * * 1" + workflow_dispatch: + +permissions: + contents: read + +jobs: + analyze: + # A called workflow cannot hold more permission than its caller grants. + permissions: + actions: read + contents: read + security-events: write + uses: trsdn/.github/.github/workflows/codeql.yml@main + with: + languages: '["actions","python"]' diff --git a/.github/workflows/docs-autogen.yml b/.github/workflows/docs-autogen.yml index 3041846..2780b69 100644 --- a/.github/workflows/docs-autogen.yml +++ b/.github/workflows/docs-autogen.yml @@ -180,7 +180,7 @@ jobs: " - name: Create Pull Request - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6 with: branch: docs/auto-generate-api title: "📚 Auto-generate missing API documentation" @@ -284,7 +284,7 @@ jobs: - name: Create issue for missing docs if: success() - uses: peter-evans/create-issue-from-file@v4 + uses: peter-evans/create-issue-from-file@433e51abf769039ee20ba1293a088ca19d573b7f # v4 with: title: "📚 Missing Documentation Report" content-filepath: docs/missing-docs-stubs.md diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ac29911..09f1454 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -98,7 +98,7 @@ jobs: - uses: actions/checkout@v4 - name: Check links in Markdown/HTML - uses: lycheeverse/lychee-action@v2 + uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2 with: args: > --no-progress @@ -119,7 +119,7 @@ jobs: codespell --check-filenames || true - name: Lint Markdown files - uses: DavidAnson/markdownlint-cli2-action@v20 + uses: DavidAnson/markdownlint-cli2-action@992badcdf24e3b8eb7e87ff9287fe931bcb00c6e # v20 continue-on-error: true with: config: .markdownlint.yml diff --git a/.github/workflows/pr-feedback.yml b/.github/workflows/pr-feedback.yml index 9dbd3cc..cbda188 100644 --- a/.github/workflows/pr-feedback.yml +++ b/.github/workflows/pr-feedback.yml @@ -300,7 +300,7 @@ jobs: echo "security_issues=$security_issues" >> $GITHUB_OUTPUT - name: Post comprehensive PR feedback - uses: marocchino/sticky-pull-request-comment@v2 + uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2 with: header: pr-analysis recreate: true diff --git a/.github/workflows/pr-summary.yml b/.github/workflows/pr-summary.yml index 0e6870c..4392fc0 100644 --- a/.github/workflows/pr-summary.yml +++ b/.github/workflows/pr-summary.yml @@ -308,7 +308,7 @@ jobs: EOF - name: Post summary comment - uses: marocchino/sticky-pull-request-comment@v2 + uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2 continue-on-error: true with: number: ${{ needs.collect-results.outputs.pr-number }} diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 9c41567..6c0d459 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -334,7 +334,7 @@ jobs: path: dist/ - name: Publish to Test PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1 with: repository-url: https://test.pypi.org/legacy/ packages-dir: dist/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5fecd8b..b780392 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -445,7 +445,7 @@ jobs: path: dist/ - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1 with: packages-dir: dist/ diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml new file mode 100644 index 0000000..dd1ff43 --- /dev/null +++ b/.github/workflows/secret-scan.yml @@ -0,0 +1,17 @@ +name: Secret scan + +on: + push: + branches: [main] + pull_request: + schedule: + - cron: "17 4 * * 1" + workflow_dispatch: + +permissions: + contents: read + pull-requests: read + +jobs: + scan: + uses: trsdn/.github/.github/workflows/secret-scan.yml@main diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index f66a08f..cf1e944 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -10,6 +10,10 @@ on: - 'pyproject.toml' - 'requirements*.txt' - '.github/workflows/security.yml' +permissions: + contents: read + pull-requests: read + # Cancel duplicate runs concurrency: group: security-${{ github.ref }} @@ -30,7 +34,7 @@ jobs: fetch-depth: 0 # Fetch full history for comprehensive scanning - name: Run GitLeaks - uses: gitleaks/gitleaks-action@v2 + uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Optional: for GitLeaks+ features diff --git a/.github/workflows/stats.yml b/.github/workflows/stats.yml new file mode 100644 index 0000000..ae51f72 --- /dev/null +++ b/.github/workflows/stats.yml @@ -0,0 +1,27 @@ +name: Repository stats + +on: + workflow_dispatch: + schedule: + - cron: "23 5 * * *" + push: + branches: + # Change this if the repository's default branch is not `main`. + - main + paths: + - .github/workflows/stats.yml + +permissions: + contents: write + +jobs: + stats: + uses: trsdn/.github/.github/workflows/repo-stats.yml@main + with: + branch: stats + output-dir: .github/stats + theme: both + cards: repo-card + commit-message: "chore(stats): update repository stats" + secrets: + STATS_TOKEN: ${{ secrets.STATS_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 89b2010..b850f91 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,6 +23,9 @@ on: - performance - security +permissions: + contents: read + # Cancel duplicate runs concurrency: group: test-${{ github.ref }} @@ -115,7 +118,7 @@ jobs: - name: Upload coverage to Codecov if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11' - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@ab904c41d6ece82784817410c45d8b8c02684457 # v3 with: file: ./coverage.xml flags: unittests diff --git a/README.md b/README.md index d9a5413..700c079 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ # 📄 MarkItDown MCP Server -[![MCP](https://img.shields.io/badge/Model_Context_Protocol-MCP-blue)](https://modelcontextprotocol.io) +[![License](https://img.shields.io/github/license/trsdn/markitdown-mcp)](LICENSE) +[![Python](https://img.shields.io/pypi/pyversions/trsdn-markitdown-mcp)](pyproject.toml) +[![CI](https://github.com/trsdn/markitdown-mcp/actions/workflows/ci-gates.yml/badge.svg?branch=main)](https://github.com/trsdn/markitdown-mcp/actions/workflows/ci-gates.yml) [![PyPI](https://img.shields.io/pypi/v/trsdn-markitdown-mcp.svg)](https://pypi.org/project/trsdn-markitdown-mcp/) -[![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](https://python.org) -[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) -[![CI](https://github.com/trsdn/markitdown-mcp/workflows/CI/badge.svg)](https://github.com/trsdn/markitdown-mcp/actions) -[![Contributions Welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](CONTRIBUTING.md) +[![MCP](https://img.shields.io/badge/Model_Context_Protocol-MCP-blue)](https://modelcontextprotocol.io) A powerful **Model Context Protocol (MCP) server** that converts 29+ file formats to clean, structured Markdown using Microsoft's MarkItDown library.