diff --git a/.github/workflows/pr-feedback.yml b/.github/workflows/pr-feedback.yml index fad38ed..9dbd3cc 100644 --- a/.github/workflows/pr-feedback.yml +++ b/.github/workflows/pr-feedback.yml @@ -64,7 +64,12 @@ jobs: # =================== echo "### ๐ŸŽจ Code Formatting" >> pr-feedback.md if ! ruff format --check . > format_output.txt 2>&1; then - format_issues=$(ruff format --check . 2>&1 | grep -c "would reformat" || echo "0") + # `grep -c` prints 0 AND exits 1 when nothing matches, so `|| echo "0"` + # produced the literal string "0\n0" and broke the arithmetic below. + # Recent ruff versions also report "N files would be reformatted", so + # parse the summary line instead of counting per-file markers. + format_issues=$(grep -oE '[0-9]+ files? would be reformatted' format_output.txt | grep -oE '^[0-9]+' | tail -1 || true) + format_issues=${format_issues:-1} echo "โŒ **$format_issues file(s) need formatting**" >> pr-feedback.md echo "" >> pr-feedback.md echo "
Click to see formatting issues" >> pr-feedback.md diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 3dec991..41978b2 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -170,7 +170,7 @@ jobs: changelog.append('') changelog.append('### ๐Ÿงช Testing') - changelog.append('- Install: \`pip install markitdown-mcp==$VERSION\`') + changelog.append('- Install: \`pip install trsdn-markitdown-mcp==$VERSION\`') changelog.append('- Feedback: Report issues in GitHub with pre-release label') changelog.append('') @@ -345,7 +345,7 @@ jobs: # Install from Test PyPI pip install --index-url https://test.pypi.org/simple/ \ --extra-index-url https://pypi.org/simple/ \ - markitdown-mcp==$VERSION + trsdn-markitdown-mcp==$VERSION # Test basic functionality python -c " @@ -383,7 +383,7 @@ jobs: This is a pre-release version for testing purposes. ๐Ÿงช **Testing Instructions:** - - Install: pip install markitdown-mcp==${{ needs.prepare-prerelease.outputs.version }} + - Install: pip install trsdn-markitdown-mcp==${{ needs.prepare-prerelease.outputs.version }} - Test your workflows with this version - Report any issues with the 'pre-release' label @@ -404,7 +404,7 @@ jobs: ### Installation ```bash - pip install markitdown-mcp==$VERSION + pip install trsdn-markitdown-mcp==$VERSION ``` ### Testing Checklist @@ -453,7 +453,7 @@ jobs: VERSION="${{ needs.prepare-prerelease.outputs.version }}" # Install from PyPI - pip install markitdown-mcp==$VERSION + pip install trsdn-markitdown-mcp==$VERSION # Comprehensive functionality test python -c " @@ -498,7 +498,7 @@ jobs: echo "" echo "๐Ÿ“ฆ **Version**: $TAG" echo "๐Ÿ”— **Release**: https://github.com/${{ github.repository }}/releases/tag/$TAG" - echo "๐Ÿ“ฅ **Install**: pip install markitdown-mcp==$VERSION" + echo "๐Ÿ“ฅ **Install**: pip install trsdn-markitdown-mcp==$VERSION" echo "" if [[ "${{ needs.create-prerelease.result }}" == "success" ]]; then diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 65526ff..8dd5061 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,11 +4,17 @@ on: push: tags: - 'v*.*.*' - + workflow_dispatch: + inputs: + tag: + description: 'Existing release tag to (re-)run the pipeline for, e.g. v1.2.3' + required: true + type: string + +# Least privilege by default; jobs opt into what they need. +# `id-token: write` is granted ONLY to the publish job (PyPI Trusted Publishing). permissions: - contents: write - id-token: write - packages: write + contents: read # Ensure only one release runs at a time concurrency: @@ -30,11 +36,12 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + ref: ${{ inputs.tag || github.ref }} - name: Extract version from tag id: version run: | - TAG=${GITHUB_REF#refs/tags/} + TAG="${{ inputs.tag || github.ref_name }}" VERSION=${TAG#v} echo "tag=${TAG}" >> $GITHUB_OUTPUT @@ -80,6 +87,8 @@ jobs: python-version: ['3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag || github.ref }} - uses: actions/setup-python@v5 with: @@ -135,6 +144,8 @@ jobs: needs: validate-release steps: - uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag || github.ref }} - uses: actions/setup-python@v5 with: @@ -176,6 +187,8 @@ jobs: package-version: ${{ steps.build.outputs.package-version }} steps: - uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag || github.ref }} - uses: actions/setup-python@v5 with: @@ -188,9 +201,26 @@ jobs: - name: Build package id: build run: | - # Update version in pyproject.toml to match tag + # Update version in pyproject.toml to match tag. + # NOTE: a plain `sed s/version = ".*"/.../` also rewrites `python_version` + # and `target-version` in the [tool.*] sections, so anchor on the line start + # and replace exactly one occurrence. VERSION="${{ needs.validate-release.outputs.version }}" - sed -i "s/version = \".*\"/version = \"$VERSION\"/" pyproject.toml + python - "$VERSION" <<'PY' + import pathlib + import re + import sys + + version = sys.argv[1] + path = pathlib.Path("pyproject.toml") + text = path.read_text(encoding="utf-8") + text, count = re.subn( + r'^version = ".*"$', f'version = "{version}"', text, count=1, flags=re.M + ) + if count != 1: + raise SystemExit("Could not update [project] version in pyproject.toml") + path.write_text(text, encoding="utf-8") + PY # Build package python -m build @@ -212,18 +242,35 @@ jobs: # Test basic functionality python -c " + from importlib.metadata import version + import markitdown_mcp - print(f'โœ… Package installed successfully: {markitdown_mcp.__version__}') + + print(f'โœ… Package installed successfully: {version(\"trsdn-markitdown-mcp\")}') + print(f' import package: {markitdown_mcp.__name__}') " - # Test MCP server - echo '{}' | markitdown-mcp || echo "โš ๏ธ MCP server test skipped" + # Test every documented entrypoint against a real MCP request + REQUEST='{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' + echo "$REQUEST" | timeout 60 markitdown-mcp | tee /tmp/tools-list.json + echo "$REQUEST" | timeout 60 trsdn-markitdown-mcp > /dev/null + echo "$REQUEST" | timeout 60 python -m markitdown_mcp > /dev/null + + python -c " + import json + import pathlib + + tools = json.loads(pathlib.Path('/tmp/tools-list.json').read_text())['result']['tools'] + assert len(tools) >= 3, f'expected 3+ tools, got {len(tools)}' + print(f'โœ… MCP entrypoints OK: {len(tools)} tools') + " - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: dist-${{ needs.validate-release.outputs.version }} path: dist/ + if-no-files-found: error retention-days: 30 # ========================================== @@ -240,6 +287,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + ref: ${{ inputs.tag || github.ref }} - name: Generate changelog from commits id: changelog @@ -370,14 +418,19 @@ jobs: " # ========================================== - # Publish to PyPI + # Publish to PyPI (Trusted Publishing / OIDC) # ========================================== - publish-pypi: + # Publisher configuration on PyPI must match EXACTLY: + # repository: trsdn/markitdown-mcp | workflow: release.yml | environment: pypi + publish: name: Publish to PyPI runs-on: ubuntu-latest - needs: [validate-release, build-package, generate-changelog] + needs: [validate-release, quality-gates, security-scan, build-package, generate-changelog] if: needs.validate-release.outputs.is-prerelease == 'false' - environment: release + environment: pypi + # No API tokens: authentication happens via short-lived OIDC credentials. + permissions: + id-token: write steps: - name: Download build artifacts uses: actions/download-artifact@v4 @@ -394,23 +447,37 @@ jobs: run: | sleep 30 # Wait for PyPI to update - pip install markitdown-mcp==${{ needs.validate-release.outputs.version }} + pip install trsdn-markitdown-mcp==${{ needs.validate-release.outputs.version }} python -c " - import markitdown_mcp - print(f'โœ… Successfully published to PyPI: v{markitdown_mcp.__version__}') + from importlib.metadata import version + + import markitdown_mcp # noqa: F401 + + print(f'โœ… Successfully published to PyPI: v{version(\"trsdn-markitdown-mcp\")}') " # ========================================== - # Create GitHub Release + # Create GitHub Release (after the PyPI publish) # ========================================== create-github-release: name: Create GitHub Release runs-on: ubuntu-latest - needs: [validate-release, build-package, generate-changelog, publish-pypi] - if: always() && needs.validate-release.result == 'success' + needs: [validate-release, build-package, generate-changelog, publish] + # `always()` so prereleases (where `publish` is skipped) still get a GitHub + # release, but never when a required upstream job actually failed. + if: | + always() + && needs.validate-release.result == 'success' + && needs.build-package.result == 'success' + && needs.generate-changelog.result == 'success' + && (needs.publish.result == 'success' || needs.publish.result == 'skipped') + permissions: + contents: write steps: - uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag || github.ref }} - name: Download build artifacts uses: actions/download-artifact@v4 @@ -443,11 +510,14 @@ jobs: runs-on: ubuntu-latest needs: [validate-release, generate-changelog, create-github-release] if: needs.validate-release.outputs.is-prerelease == 'false' + permissions: + contents: write steps: - uses: actions/checkout@v4 with: token: ${{ secrets.GITHUB_TOKEN }} fetch-depth: 0 + ref: main - name: Update CHANGELOG.md run: | @@ -492,7 +562,7 @@ jobs: post-release-validation: name: Post-Release Validation runs-on: ubuntu-latest - needs: [validate-release, create-github-release, publish-pypi, update-docs] + needs: [validate-release, create-github-release, publish, update-docs] if: always() steps: - name: Validate release completion @@ -512,7 +582,7 @@ jobs: # Check PyPI (skip for prereleases) if [[ "${{ needs.validate-release.outputs.is-prerelease }}" == "false" ]]; then - if pip index versions markitdown-mcp | grep -q "$VERSION"; then + if pip index versions trsdn-markitdown-mcp | grep -q "$VERSION"; then echo "โœ… PyPI package published" else echo "โŒ PyPI package missing" diff --git a/AGENTS.md b/AGENTS.md index c0df831..1da8a3f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -105,7 +105,7 @@ def analyze_document(file_path): result = convert_file(file_path=file_path) # Step 2: Extract and analyze content - markdown_content = result['content'][0]['text'] + markdown_content = result["content"][0]["text"] # Step 3: Provide analysis to user return analyze_markdown(markdown_content) @@ -115,10 +115,7 @@ def analyze_document(file_path): ```python def process_document_folder(input_dir, output_dir): # Convert entire directory - result = convert_directory( - input_directory=input_dir, - output_directory=output_dir - ) + result = convert_directory(input_directory=input_dir, output_directory=output_dir) # Report results to user return summarize_conversion_results(result) @@ -174,10 +171,10 @@ convert_file("/etc/passwd") formats = list_supported_formats() if is_supported(file_path, formats): result = convert_file(file_path) - if result.get('error'): - handle_conversion_error(result['error']) + if result.get("error"): + handle_conversion_error(result["error"]) else: - process_markdown(result['content']) + process_markdown(result["content"]) ``` ## ๐Ÿ” Troubleshooting Guide @@ -271,13 +268,10 @@ class DocumentConverter: async def convert_and_analyze(self, file_path): # Convert document - result = await self.mcp.call_tool( - "convert_file", - {"file_path": file_path} - ) + result = await self.mcp.call_tool("convert_file", {"file_path": file_path}) # Extract content - markdown = result['content'][0]['text'] + markdown = result["content"][0]["text"] # Perform analysis return self.analyze_content(markdown) @@ -341,18 +335,18 @@ def analyze_version_impact(commits_since_last_release): Returns: 'major', 'minor', 'patch', or 'none' """ - has_breaking = any('BREAKING CHANGE' in commit.body for commit in commits) - has_features = any(commit.type == 'feat' for commit in commits) - has_fixes = any(commit.type in ['fix', 'perf'] for commit in commits) + has_breaking = any("BREAKING CHANGE" in commit.body for commit in commits) + has_features = any(commit.type == "feat" for commit in commits) + has_fixes = any(commit.type in ["fix", "perf"] for commit in commits) if has_breaking: - return 'major' + return "major" elif has_features: - return 'minor' + return "minor" elif has_fixes: - return 'patch' + return "patch" else: - return 'none' + return "none" ``` ### Release Readiness Validation @@ -363,13 +357,13 @@ Before suggesting releases, AI agents should verify: def validate_release_readiness(): """Check if codebase is ready for release.""" checks = { - 'format_check': run_ruff_format(), - 'lint_check': run_ruff_lint(), - 'type_check': run_mypy(), - 'tests_pass': run_test_suite(), - 'coverage_ok': check_coverage_threshold(), - 'docs_updated': verify_documentation(), - 'security_clean': run_security_scan() + "format_check": run_ruff_format(), + "lint_check": run_ruff_lint(), + "type_check": run_mypy(), + "tests_pass": run_test_suite(), + "coverage_ok": check_coverage_threshold(), + "docs_updated": verify_documentation(), + "security_clean": run_security_scan(), } failed_checks = [name for name, passed in checks.items() if not passed] @@ -389,9 +383,9 @@ def generate_changelog_entry(commits, version): changelog = f"## [{version}] - {datetime.now().strftime('%Y-%m-%d')}\n\n" # Group commits by type - features = [c for c in commits if c.type == 'feat'] - fixes = [c for c in commits if c.type == 'fix'] - breaking = [c for c in commits if 'BREAKING CHANGE' in c.body] + features = [c for c in commits if c.type == "feat"] + fixes = [c for c in commits if c.type == "fix"] + breaking = [c for c in commits if "BREAKING CHANGE" in c.body] if breaking: changelog += "### โš ๏ธ BREAKING CHANGES\n" @@ -435,7 +429,7 @@ async def suggest_release_if_needed(): # Analyze version impact version_impact = analyze_version_impact(commits) - if version_impact == 'none': + if version_impact == "none": return "No release needed - no significant changes" # Validate readiness @@ -451,11 +445,11 @@ async def suggest_release_if_needed(): changelog = generate_changelog_entry(commits, new_version) return { - 'action': 'suggest_release', - 'version': new_version, - 'changelog': changelog, - 'commits_included': len(commits), - 'impact': version_impact + "action": "suggest_release", + "version": new_version, + "changelog": changelog, + "commits_included": len(commits), + "impact": version_impact, } ``` @@ -467,20 +461,20 @@ AI agents should communicate release information clearly: def format_release_summary(release_info): """Format release information for users.""" return f""" -๐Ÿš€ **Release Suggestion: v{release_info['version']}** +๐Ÿš€ **Release Suggestion: v{release_info["version"]}** -**Impact**: {release_info['impact'].title()} version bump -**Changes**: {release_info['commits_included']} commits included -**Type**: {'๐Ÿ”ฅ Breaking changes' if release_info['impact'] == 'major' else 'โœจ New features' if release_info['impact'] == 'minor' else '๐Ÿ› Bug fixes'} +**Impact**: {release_info["impact"].title()} version bump +**Changes**: {release_info["commits_included"]} commits included +**Type**: {"๐Ÿ”ฅ Breaking changes" if release_info["impact"] == "major" else "โœจ New features" if release_info["impact"] == "minor" else "๐Ÿ› Bug fixes"} **Changelog Preview**: -{release_info['changelog'][:500]}... +{release_info["changelog"][:500]}... **Next Steps**: 1. Review the proposed changes 2. Run final quality checks -3. Create release tag: `git tag v{release_info['version']}` -4. Push tag to trigger automated release: `git push origin v{release_info['version']}` +3. Create release tag: `git tag v{release_info["version"]}` +4. Push tag to trigger automated release: `git push origin v{release_info["version"]}` """ ``` @@ -496,7 +490,7 @@ For critical security issues, AI agents should: ```python def detect_emergency_release(commits): """Detect if commits contain critical security fixes.""" - security_keywords = ['security', 'vulnerability', 'cve', 'exploit', 'xss', 'injection'] + security_keywords = ["security", "vulnerability", "cve", "exploit", "xss", "injection"] for commit in commits: commit_text = f"{commit.description} {commit.body}".lower() @@ -670,18 +664,15 @@ def create_new_workflow(name, triggers, jobs): """Template for creating new GitHub Actions workflows.""" workflow = { - 'name': name, - 'on': triggers, - 'permissions': { - 'contents': 'read', - 'pull-requests': 'write' - }, - 'jobs': jobs + "name": name, + "on": triggers, + "permissions": {"contents": "read", "pull-requests": "write"}, + "jobs": jobs, } # Validate before writing yaml_content = yaml.dump(workflow, default_flow_style=False) - return safe_workflow_update(f'.github/workflows/{name.lower()}.yml', yaml_content) + return safe_workflow_update(f".github/workflows/{name.lower()}.yml", yaml_content) ``` **Updating existing workflow**: @@ -690,7 +681,7 @@ def update_workflow_safely(file_path, updates): """Safely update existing workflow with changes.""" # Read current workflow - with open(file_path, 'r') as f: + with open(file_path, "r") as f: workflow = yaml.safe_load(f) # Apply updates diff --git a/CHANGELOG.md b/CHANGELOG.md index 62f281c..72bfade 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Changed +- **Renamed the PyPI distribution to `trsdn-markitdown-mcp`** (the name `markitdown-mcp` + is taken by Microsoft's official project). The import package (`markitdown_mcp`) and the + `markitdown-mcp` console command are unchanged. +- Release workflow now publishes via PyPI Trusted Publishing (OIDC, `environment: pypi`) + instead of API tokens. +- `MANIFEST.in` prunes tests, docs, examples, scripts and schemas from the published + sdist. + +### Added +- `trsdn-markitdown-mcp` console-script alias and `python -m markitdown_mcp` support. +- Packaging metadata: SPDX `license`, `license-files`, maintainers, extra classifiers, + keywords and project URLs. + ## [1.2.2] - 2026-06-10 ### Fixed diff --git a/MANIFEST.in b/MANIFEST.in index 8c346be..33e876e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,22 @@ include README.md +include LICENSE +include CHANGELOG.md include requirements.txt -include mcp_config.json recursive-include markitdown_mcp *.py + +# Keep development-only assets out of the published distributions +prune tests +prune docs +prune examples +prune scripts +prune schemas +prune .github +exclude .env +exclude .env.example +exclude pytest.ini +exclude requirements-all.txt +exclude AGENTS.md +exclude RELEASE.md global-exclude *.pyc global-exclude __pycache__ -global-exclude .DS_Store \ No newline at end of file +global-exclude .DS_Store diff --git a/README.md b/README.md index a55e112..d9a5413 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # ๐Ÿ“„ MarkItDown MCP Server [![MCP](https://img.shields.io/badge/Model_Context_Protocol-MCP-blue)](https://modelcontextprotocol.io) +[![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) @@ -8,6 +9,19 @@ A powerful **Model Context Protocol (MCP) server** that converts 29+ file formats to clean, structured Markdown using Microsoft's MarkItDown library. +> [!IMPORTANT] +> **This is not Microsoft's official `markitdown-mcp` package.** +> This is an independent community project published on PyPI as +> **[`trsdn-markitdown-mcp`](https://pypi.org/project/trsdn-markitdown-mcp/)**. +> It *uses* Microsoft's [`markitdown`](https://pypi.org/project/markitdown/) library as a +> dependency, but it is developed and maintained separately from +> [`markitdown-mcp`](https://pypi.org/project/markitdown-mcp/) by Microsoft. +> +> - **PyPI distribution name**: `trsdn-markitdown-mcp` +> - **Python import name**: `markitdown_mcp` +> - **CLI command**: `markitdown-mcp` (alias: `trsdn-markitdown-mcp`) + + ๐Ÿ”ฅ **Perfect for Claude Desktop, MCP clients, and AI workflows!** ## โœจ Features @@ -23,11 +37,11 @@ A powerful **Model Context Protocol (MCP) server** that converts 29+ file format | File Type | Required Dependencies | Install Command | |-----------|----------------------|-----------------| -| **PDF** | `pypdf`, `pymupdf`, `pdfplumber` | `pipx inject markitdown-mcp 'markitdown[all]'` | -| **Excel (.xlsx, .xls)** | `openpyxl`, `xlrd`, `pandas` | `pipx inject markitdown-mcp openpyxl xlrd pandas` | +| **PDF** | `pypdf`, `pymupdf`, `pdfplumber` | `pipx inject trsdn-markitdown-mcp 'markitdown[all]'` | +| **Excel (.xlsx, .xls)** | `openpyxl`, `xlrd`, `pandas` | `pipx inject trsdn-markitdown-mcp openpyxl xlrd pandas` | | **PowerPoint (.pptx)** | `python-pptx` | Included in base install | | **Images** | `PIL`, `exiftool` (optional) | Included in base install | -| **Audio** | `pydub`, `speech_recognition` | `pipx inject markitdown-mcp 'markitdown[all]'` | +| **Audio** | `pydub`, `speech_recognition` | `pipx inject trsdn-markitdown-mcp 'markitdown[all]'` | | **Basic formats** | None | Base install only | **Note**: For the best experience, we recommend installing all dependencies using the **Complete Install** method below. @@ -41,8 +55,8 @@ A powerful **Model Context Protocol (MCP) server** that converts 29+ file format 1. **Install the server with ALL features:** ```bash # One command to install everything - pipx install git+https://github.com/trsdn/markitdown-mcp.git && \ - pipx inject markitdown-mcp 'markitdown[all]' openpyxl xlrd pandas pymupdf pdfplumber + pipx install trsdn-markitdown-mcp && \ + pipx inject trsdn-markitdown-mcp 'markitdown[all]' openpyxl xlrd pandas pymupdf pdfplumber ``` 2. **Add to your Claude Desktop config:** @@ -114,22 +128,34 @@ Convert all supported files in a directory. ## Installation -### Option 1: Pip Install (Recommended) +> Published on PyPI as **`trsdn-markitdown-mcp`** โ€” not to be confused with Microsoft's `markitdown-mcp`. + +### Option 1: Install from PyPI (Recommended) ```bash -# Install from local directory -pip install -e /Users/torstenmahr/GitHub/markitdown-mcp +# Isolated install with pipx +pipx install trsdn-markitdown-mcp + +# Or with pip +pip install trsdn-markitdown-mcp + +# Or run without installing (uv) +uvx trsdn-markitdown-mcp +``` -# Or navigate to the directory first -cd /Users/torstenmahr/GitHub/markitdown-mcp -pip install -e . +### Option 2: Install from source (development) + +```bash +git clone https://github.com/trsdn/markitdown-mcp.git +cd markitdown-mcp +pip install -e ".[all]" ``` -### Option 2: Direct Usage +### Option 3: Direct usage from a checkout ```bash -cd /Users/torstenmahr/GitHub/markitdown-mcp -source venv/bin/activate +cd markitdown-mcp +python -m venv venv && source venv/bin/activate pip install -r requirements.txt ``` @@ -137,15 +163,14 @@ pip install -r requirements.txt ### MCP Server Mode (Recommended) -After pip installation: +After installation: ```bash # Start the MCP server (for use with MCP clients) markitdown-mcp -``` -Or using the development script: -```bash -python run_server.py +# Equivalent alternatives +trsdn-markitdown-mcp +python -m markitdown_mcp ``` ## ๐Ÿ› ๏ธ Installation Options @@ -154,8 +179,8 @@ python run_server.py Install with ALL dependencies in one command: ```bash # Using pipx (recommended) -pipx install git+https://github.com/trsdn/markitdown-mcp.git && \ -pipx inject markitdown-mcp 'markitdown[all]' openpyxl xlrd pandas pymupdf pdfplumber pytesseract pydub speechrecognition +pipx install trsdn-markitdown-mcp && \ +pipx inject trsdn-markitdown-mcp 'markitdown[all]' openpyxl xlrd pandas pymupdf pdfplumber pytesseract pydub speechrecognition # Or download and run the install script curl -sSL https://raw.githubusercontent.com/trsdn/markitdown-mcp/main/scripts/install-all-deps.sh | bash @@ -163,7 +188,7 @@ curl -sSL https://raw.githubusercontent.com/trsdn/markitdown-mcp/main/scripts/in ### Quick Install (Basic Features Only) ```bash -pip install -e git+https://github.com/trsdn/markitdown-mcp.git +pip install trsdn-markitdown-mcp ``` ### Complete Install with All Dependencies (Step by Step) @@ -173,12 +198,12 @@ To ensure all file formats are supported, use one of these methods: #### Method 1: Using pipx (Recommended) ```bash # Install the MCP server -pipx install git+https://github.com/trsdn/markitdown-mcp.git +pipx install trsdn-markitdown-mcp # Install all required dependencies for full functionality -pipx inject markitdown-mcp 'markitdown[all]' # PDF, OCR, Speech -pipx inject markitdown-mcp openpyxl xlrd pandas # Excel support -pipx inject markitdown-mcp pymupdf pdfplumber # Advanced PDF +pipx inject trsdn-markitdown-mcp 'markitdown[all]' # PDF, OCR, Speech +pipx inject trsdn-markitdown-mcp openpyxl xlrd pandas # Excel support +pipx inject trsdn-markitdown-mcp pymupdf pdfplumber # Advanced PDF ``` #### Method 2: Using pip with virtual environment @@ -200,14 +225,14 @@ If you already have the MCP server installed but some formats aren't working: which markitdown-mcp # Shows path like /Users/you/.local/bin/markitdown-mcp # Inject missing dependencies -pipx inject markitdown-mcp 'markitdown[all]' openpyxl xlrd pandas pymupdf pdfplumber +pipx inject trsdn-markitdown-mcp 'markitdown[all]' openpyxl xlrd pandas pymupdf pdfplumber ``` ### Verify Installation -After installation, verify all dependencies are properly installed: +After installation, verify the server responds to MCP requests: ```bash -# Test the MCP server -markitdown-mcp --help +# Ask the server for its tool list over stdio (JSON-RPC) +echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | markitdown-mcp # For pipx installations, check injected packages pipx list --include-injected @@ -228,6 +253,20 @@ Add this to your Claude Desktop `claude_desktop_config.json`: } ``` +Prefer running it without a permanent install? Use `uvx` (the package name is +`trsdn-markitdown-mcp`, the command is `markitdown-mcp`): + +```json +{ + "mcpServers": { + "markitdown": { + "command": "uvx", + "args": ["trsdn-markitdown-mcp"] + } + } +} +``` + **Config file locations:** - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` @@ -326,7 +365,6 @@ Convert all supported files in a directory. markitdown-mcp/ โ”œโ”€โ”€ mcp_server.py # MCP protocol server โ”œโ”€โ”€ mdconvert.py # CLI script -โ”œโ”€โ”€ run_server.py # Server runner script โ”œโ”€โ”€ mcp_config.json # MCP configuration โ”œโ”€โ”€ requirements.txt # Python dependencies โ”œโ”€โ”€ README.md # This file diff --git a/RELEASE.md b/RELEASE.md index 3e1b564..189245e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,6 +1,11 @@ # Release Process -This document outlines the release process for markitdown-mcp. +This document outlines the release process for `trsdn-markitdown-mcp`. + +> **Naming**: the PyPI distribution is `trsdn-markitdown-mcp`. The Python import package +> stays `markitdown_mcp` and the CLI command stays `markitdown-mcp` (with a +> `trsdn-markitdown-mcp` alias). This project is **not** Microsoft's official +> `markitdown-mcp` package. ## Version Numbering @@ -20,13 +25,45 @@ We follow [Semantic Versioning](https://semver.org/): - Include all changes, fixes, and new features 3. **Create Release** - - Tag the release: `git tag -a v1.0.0 -m "Release v1.0.0"` - - Push tags: `git push origin --tags` + - Tag the release: `git tag -a v1.2.3 -m "Release v1.2.3"` + - Push tags: `git push origin v1.2.3` + +4. **Automated Publish** + - `.github/workflows/release.yml` runs the full release chain: + `validate-release` โ†’ `quality-gates` (ruff/mypy/pytest on Python 3.10/3.11/3.12 + plus MCP protocol validation) + `security-scan` (Bandit, dependency audit) โ†’ + `build-package` (build, `twine check`, entrypoint smoke test) โ†’ + `generate-changelog` โ†’ `publish` (PyPI) โ†’ `create-github-release` โ†’ + `update-docs` โ†’ `post-release-validation`. + - The GitHub release is created **after** the PyPI publish succeeds. + +## PyPI Trusted Publishing (OIDC) + +Releases are published with [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/). +**No API tokens or secrets are stored in this repository.** + +The PyPI publisher configuration must match exactly: + +| Setting | Value | +|---------|-------| +| PyPI project | `trsdn-markitdown-mcp` | +| Owner / repository | `trsdn/markitdown-mcp` | +| Workflow file | `release.yml` | +| Environment | `pypi` | + +The `publish` job therefore declares `environment: pypi` and +`permissions: { id-token: write }`. `id-token: write` is granted **only** to that job; +the workflow-level default is `contents: read`, and `contents: write` is scoped to the +jobs that create the release and push the changelog. Changing the workflow filename, the +job's environment, or the repository slug will break OIDC authentication. -4. **Publish to PyPI** - - Build: `python -m build` - - Upload: `python -m twine upload dist/*` +### Local dry run -## Automated Releases +```bash +python -m pip install --upgrade build twine +python -m build +twine check dist/* +``` -GitHub Actions automatically publishes to PyPI when a new tag is pushed. \ No newline at end of file +Never run `twine upload` manually โ€” publishing happens only through the tagged +release workflow. diff --git a/docs/guides/KNOWN_ISSUES.md b/docs/guides/KNOWN_ISSUES.md index 93d49eb..15162a1 100644 --- a/docs/guides/KNOWN_ISSUES.md +++ b/docs/guides/KNOWN_ISSUES.md @@ -35,7 +35,7 @@ PptxConverter threw BadZipFile... **Solution**: Install the required dependencies using pipx: ```bash -pipx inject markitdown-mcp openpyxl xlrd pandas tabulate +pipx inject trsdn-markitdown-mcp openpyxl xlrd pandas tabulate ``` ## Corrupted Office Files diff --git a/docs/index.rst b/docs/index.rst index ce3b938..b3ccf4a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -83,11 +83,11 @@ Installation Install via pip:: - pip install markitdown-mcp + pip install trsdn-markitdown-mcp Or with all optional dependencies:: - pip install markitdown-mcp[all] + pip install trsdn-markitdown-mcp[all] Quick Example ------------- diff --git a/markitdown_mcp/__main__.py b/markitdown_mcp/__main__.py new file mode 100644 index 0000000..be5dfcd --- /dev/null +++ b/markitdown_mcp/__main__.py @@ -0,0 +1,6 @@ +"""Allow running the MCP server via ``python -m markitdown_mcp``.""" + +from .server import main + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml index 7a0e62a..7e169d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,29 +1,39 @@ [build-system] requires = [ - "setuptools>=61.0", + "setuptools>=77.0", "wheel", ] build-backend = "setuptools.build_meta" [project] -name = "markitdown-mcp" +name = "trsdn-markitdown-mcp" version = "1.2.2" -description = "A Model Context Protocol server for converting documents to Markdown using MarkItDown" +description = "An independent Model Context Protocol (MCP) server for converting documents to Markdown using MarkItDown" authors = [ - { name = "MarkItDown MCP", email = "noreply@example.com" }, + { name = "trsdn", email = "noreply@users.noreply.github.com" }, +] +maintainers = [ + { name = "trsdn", email = "noreply@users.noreply.github.com" }, ] readme = "README.md" +license = "MIT" +license-files = [ + "LICENSE", +] requires-python = ">=3.10" classifiers = [ "Development Status :: 4 - Beta", + "Environment :: Console", "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Text Processing :: Markup :: Markdown", "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed", ] keywords = [ "mcp", @@ -31,6 +41,10 @@ keywords = [ "markdown", "document-conversion", "model-context-protocol", + "pdf", + "docx", + "ocr", + "llm", ] dependencies = [ "markitdown>=0.1.0", @@ -42,9 +56,6 @@ dependencies = [ "python-dotenv>=1.0.0", ] -[project.license] -text = "MIT" - [project.optional-dependencies] all = [ "markitdown[all]>=0.1.0", @@ -92,11 +103,14 @@ docs = [ [project.urls] Homepage = "https://github.com/trsdn/markitdown-mcp" +Documentation = "https://github.com/trsdn/markitdown-mcp#readme" Repository = "https://github.com/trsdn/markitdown-mcp" Issues = "https://github.com/trsdn/markitdown-mcp/issues" +Changelog = "https://github.com/trsdn/markitdown-mcp/blob/main/CHANGELOG.md" [project.scripts] markitdown-mcp = "markitdown_mcp.server:main" +trsdn-markitdown-mcp = "markitdown_mcp.server:main" [tool.setuptools.packages.find] where = [ diff --git a/scripts/install-all-deps.sh b/scripts/install-all-deps.sh index 75c86a1..d4b3998 100755 --- a/scripts/install-all-deps.sh +++ b/scripts/install-all-deps.sh @@ -17,7 +17,7 @@ fi # Install or reinstall the MCP server echo "๐Ÿ“ฆ Installing MarkItDown MCP Server..." -pipx install --force git+https://github.com/trsdn/markitdown-mcp.git +pipx install --force trsdn-markitdown-mcp # Install all required dependencies echo "" @@ -25,19 +25,19 @@ echo "๐Ÿ’‰ Injecting all required dependencies..." # Core MarkItDown dependencies echo " โ†’ Installing PDF, OCR, and Speech dependencies..." -pipx inject markitdown-mcp 'markitdown[all]' --force +pipx inject trsdn-markitdown-mcp 'markitdown[all]' --force # Excel support echo " โ†’ Installing Excel support..." -pipx inject markitdown-mcp openpyxl xlrd pandas tabulate --force +pipx inject trsdn-markitdown-mcp openpyxl xlrd pandas tabulate --force # Advanced PDF support echo " โ†’ Installing advanced PDF support..." -pipx inject markitdown-mcp pymupdf pdfplumber pytesseract --force +pipx inject trsdn-markitdown-mcp pymupdf pdfplumber pytesseract --force # Audio/video support echo " โ†’ Installing audio processing support..." -pipx inject markitdown-mcp pydub speechrecognition --force +pipx inject trsdn-markitdown-mcp pydub speechrecognition --force echo "" echo "โœ… Installation complete!"