Skip to content

feat(packaging)!: publish as trsdn-markitdown-mcp via PyPI Trusted Publishing - #48

Merged
trsdn merged 3 commits into
mainfrom
trsdn-pypi-trusted-publishing
Aug 24, 2026
Merged

trsdn merged 3 commits into
mainfrom
trsdn-pypi-trusted-publishing

Conversation

@trsdn

@trsdn trsdn commented Aug 24, 2026 •

Copy link
Copy Markdown
Owner

Why

The PyPI name markitdown-mcp is already taken by Microsoft's official MarkItDown project (v0.0.1a4). This repository is an independent project, so it will be published as trsdn-markitdown-mcp (verified available on PyPI) using Trusted Publishing (GitHub Actions OIDC) — no API tokens.

PyPI publisher configuration (must match exactly)

Setting Value
PyPI project trsdn-markitdown-mcp
Owner/repository trsdn/markitdown-mcp
Workflow file release.yml
Environment pypi

Changes

Packaging (pyproject.toml)

  • name → trsdn-markitdown-mcp. The import package stays markitdown_mcp; [tool.setuptools.packages.find] still resolves exactly one top-level package (verified via top_level.txt).
  • Completed metadata: SPDX license = "MIT" + license-files, maintainers, extra classifiers, keywords, requires-python, richer [project.urls]. Removed the deprecated [project.license] text table and the redundant license classifier (setuptools>=77).
  • [project.scripts]: markitdown-mcp kept, plus a trsdn-markitdown-mcp alias so uvx trsdn-markitdown-mcp works directly.
  • New markitdown_mcp/__main__.py → python -m markitdown_mcp works.

No import shadowing — the top-level module is markitdown_mcp, clearly distinct from the upstream markitdown dependency. top_level.txt contains only markitdown_mcp.

Release workflow (.github/workflows/release.yml) — additive, +92/−22

The existing job chain is fully preserved: validate-release, quality-gates (matrix 3.10/3.11/3.12 + MCP protocol validation), security-scan (Bandit + dependency audit), build-package, generate-changelog, create-github-release, update-docs, post-release-validation, and the concurrency group.

Added on top:

  • New publish job: needs: [validate-release, quality-gates, security-scan, build-package, generate-changelog], environment: pypi, permissions: { id-token: write }, pypa/gh-action-pypi-publish@release/v1. No secrets. Replaces the old publish-pypi job that pointed at environment: release.
  • create-github-release now needs publish, so the GitHub release is created after the PyPI upload. Its condition was tightened so prereleases (where publish is skipped) still get a release, but a failed publish does not.
  • Least-privilege permissions: workflow-level contents: read; id-token: write only in publish; contents: write only in create-github-release/update-docs; unused packages: write removed.
  • workflow_dispatch with a required tag input; checkouts resolve ref: ${{ inputs.tag || github.ref }}.

Fixes to existing steps (each justified in this comment):

  • The version sync used an unanchored sed s/version = ".*"/, which also rewrote target-version = "py310" and python_version = "3.10" in the [tool.*] sections — i.e. every release shipped a corrupted ruff/mypy config. Replaced with an anchored, single-occurrence re.subn that fails loudly.
  • echo '{}' | markitdown-mcp || echo "…skipped" could never fail ({} has no method, and || swallowed errors). Replaced with a real tools/list request asserting ≥3 tools, run against all three entrypoints.
  • Verification read markitdown_mcp.__version__ (1.0.0, drifted from the built version) → now importlib.metadata.version("trsdn-markitdown-mcp").
  • if-no-files-found: error on the artifact upload.

Distribution hygiene (MANIFEST.in)

Prunes tests/, docs/, examples/, scripts/, schemas/, .github/, .env*; removed the include of the non-existent mcp_config.json.

Docs

README gets a PyPI badge, a prominent "this is NOT Microsoft's official markitdown-mcp" callout, PyPI/uvx install instructions and an uvx MCP client config example; hardcoded local paths and the reference to the non-existent run_server.py were removed. RELEASE.md documents the Trusted Publishing setup and the full job chain. docs/index.rst, docs/guides/KNOWN_ISSUES.md, scripts/install-all-deps.sh and pre-release.yml now use the new distribution name.

Validation

$ python -m build      → trsdn_markitdown_mcp-1.2.2{.tar.gz,-py3-none-any.whl}
$ twine check dist/*   → both PASSED
$ actionlint .github/workflows/release.yml → same finding count as main (no new issues)
$ bash -n on every run block → 0 syntax errors
$ ruff format --check / ruff check markitdown_mcp/ → clean
$ pytest tests/unit -q → 109 passed

sdist contents (no tests, no .env, no sample documents; 28 KB):

CHANGELOG.md  LICENSE  MANIFEST.in  PKG-INFO  README.md  pyproject.toml
requirements.txt  setup.cfg  markitdown_mcp/{__init__,__main__,server}.py  *.egg-info/

Entrypoints verified against the installed wheel in a clean venv — markitdown-mcp, trsdn-markitdown-mcp and python -m markitdown_mcp each return the 3 MCP tools (convert_file, list_supported_formats, convert_directory).

No upload to PyPI was performed.

Follow-ups (not in this PR)

  • Create the Pending Trusted Publisher on PyPI with the values in the table above before pushing a v* tag. (The GitHub environments pypi and test-pypi already exist.)
  • pre-release.yml publishes to TestPyPI with environment: test-pypi via OIDC — that needs its own pending publisher on TestPyPI (workflow file pre-release.yml) if pre-releases are to be used.
  • Unrelated: trsdn/markitdown-mcp-server looks like an abandoned duplicate of this project (same description, no pyproject.toml). Nothing was changed there; consider archiving it to avoid confusion.

…blishing

The PyPI name `markitdown-mcp` is owned by Microsoft's official project, so
this independent server is published as `trsdn-markitdown-mcp`.

- rename the distribution to `trsdn-markitdown-mcp` (import package
  `markitdown_mcp` and the `markitdown-mcp` command are unchanged)
- add a `trsdn-markitdown-mcp` console-script alias and `__main__.py` so the
  server can be started via `uvx` or `python -m markitdown_mcp`
- complete publish metadata (SPDX license, license-files, maintainers,
  classifiers, keywords, project URLs)
- rewrite release.yml around a `build` + `publish` pair using OIDC Trusted
  Publishing (environment `pypi`, `id-token: write`, no API tokens) with
  `twine check` and a wheel smoke test before publishing
- prune tests/docs/examples/scripts/schemas from the sdist
- update README, docs and install script to the new distribution name and add
  a prominent "not Microsoft's official package" notice

BREAKING CHANGE: the PyPI distribution name changes from `markitdown-mcp` to
`trsdn-markitdown-mcp`.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Reverts the over-aggressive rewrite of release.yml. The original job chain
(validate-release, quality-gates with the 3.10/3.11/3.12 matrix, security-scan,
build-package, generate-changelog, create-github-release, update-docs,
post-release-validation) and the concurrency group are restored unchanged.

Trusted Publishing is now additive:
- new `publish` job with needs: [validate-release, quality-gates, security-scan,
  build-package, generate-changelog], `environment: pypi`,
  `permissions: { id-token: write }` and pypa/gh-action-pypi-publish, no secrets
- `create-github-release` now needs `publish`, so the GitHub release is created
  after the PyPI upload (and still runs for prereleases, where publish is skipped)
- workflow-level permissions reduced to `contents: read`; `id-token: write` only
  in `publish`, `contents: write` only in `create-github-release`/`update-docs`
- `workflow_dispatch` with a required `tag` input; all checkouts resolve
  `inputs.tag || github.ref` so a manual run builds the tagged commit
- version sync no longer uses a bare `sed s/version = ".*"/`, which also rewrote
  `target-version = "py310"` and `python_version = "3.10"` in the [tool.*]
  sections of pyproject.toml
- package verification now exercises markitdown-mcp, trsdn-markitdown-mcp and
  `python -m markitdown_mcp` with a real tools/list request, and reads the
  version via importlib.metadata; PyPI names updated to trsdn-markitdown-mcp

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@trsdn

trsdn commented Aug 24, 2026

Copy link
Copy Markdown
Owner Author

Nachgebessert: Release-Pipeline wiederhergestellt (eb0091c)

Berechtigter Einwand — das Zusammenstreichen der release.yml war ein Fehler. Ich habe die Datei aus main wiederhergestellt und Trusted Publishing additiv eingefügt. Der Diff gegenüber main ist jetzt +92/−22 statt +82/−462.

Vollständig erhalten

validate-release (Tag-Format, Dublettencheck, Version-Extraktion) · quality-gates (Matrix 3.10/3.11/3.12, ruff format/check, mypy --strict, pytest mit Coverage-Gate, MCP-Protokoll-Validierung) · security-scan (Bandit + Dependency-Audit) · build-package inkl. Verify · generate-changelog · create-github-release · update-docs · post-release-validation · die concurrency-Gruppe.

Neu / geändert

publish (neu)

publish:
  needs: [validate-release, quality-gates, security-scan, build-package, generate-changelog]
  if: needs.validate-release.outputs.is-prerelease == 'false'
  environment: pypi
  permissions:
    id-token: write

pypa/gh-action-pypi-publish@release/v1, keine Secrets. Ersetzt den alten publish-pypi-Job, der auf environment: release zeigte.

Reihenfolge — create-github-release hat jetzt needs: [..., publish], das GitHub-Release entsteht also nach dem PyPI-Upload. Die Bedingung wurde von always() && validate-release.result == 'success' verschärft auf zusätzlich build-package/generate-changelog erfolgreich und publish in success oder skipped — so bekommen Prereleases (bei denen publish übersprungen wird) weiterhin ein GitHub-Release, ein fehlgeschlagener Publish erzeugt aber keins mehr.

Permissions — workflow-weit auf contents: read. id-token: write nur in publish, contents: write nur in create-github-release und update-docs. packages: write entfernt (wurde nirgends genutzt).

Einzeln begründete Änderungen an bestehenden Schritten

  1. Version-Sync in build-package war fehlerhaft. sed -i "s/version = \".*\"/version = \"$VERSION\"/" ist nicht zeilenanfangs-verankert und ersetzt alle Treffer. Lokal reproduziert mit VERSION=1.3.0:

    vorher:  target-version = "py310"   python_version = "3.10"
    nachher: target-version = "1.3.0"   python_version = "1.3.0"
    

    Die [tool.ruff]- und [tool.mypy]-Konfiguration im gebauten sdist wurde also bei jedem Release beschädigt. Ersetzt durch re.subn(r'^version = ".*"$', ..., count=1, flags=re.M) mit Fehlerabbruch, wenn nicht genau ein Treffer.

  2. echo '{}' | markitdown-mcp || echo "MCP server test skipped" konnte nie fehlschlagen — {} ist kein gültiger JSON-RPC-Request (kein method), und der ||-Fallback schluckte jeden Fehler. Ersetzt durch einen echten tools/list-Request mit Assertion auf ≥3 Tools, ausgeführt über alle drei Entrypoints (markitdown-mcp, trsdn-markitdown-mcp, python -m markitdown_mcp).

  3. markitdown_mcp.__version__ ist 1.0.0 und driftet von der tatsächlich gebauten Version ab; die Verify-Ausgaben lasen also die falsche Zahl. Umgestellt auf importlib.metadata.version("trsdn-markitdown-mcp").

  4. workflow_dispatch hat einen Pflicht-Input tag; alle Checkouts nutzen ref: ${{ inputs.tag || github.ref }}, damit ein manueller Lauf denselben Commit baut wie der Tag-Push. update-docs checkt bewusst weiter main aus, da es den Changelog dorthin pusht.

  5. if-no-files-found: error beim Artifact-Upload, damit ein leeres dist/ nicht stillschweigend an publish durchgereicht wird.

  6. PyPI-Namen in publish und post-release-validation auf trsdn-markitdown-mcp gezogen.

Validierung

  • actionlint: identische Befundzahl wie auf main (7 vorbestehende SC2086/SC2046-Infos in unveränderten Zeilen, keine neuen).
  • Alle 9 Jobs, ihre needs-Kanten, concurrency und die Python-Matrix per YAML-Parse verifiziert.
  • Jeder run-Block per bash -n geprüft: 0 Syntaxfehler.
  • Der sed-Bug wurde lokal gegen die echte pyproject.toml reproduziert und der Fix verifiziert.

Danke für den Hinweis auf die angelegten Environments pypi und test-pypi.

Both failures are pre-existing on main and unrelated to the packaging change;
they only surfaced now because CI installs an unpinned `ruff>=0.1.0` and a newer
release changed behaviour.

- ruff now also formats Python blocks embedded in Markdown, so AGENTS.md (not
  touched by this PR) failed `ruff format --check`. Applied `ruff format`; the
  diff is purely quote/wrapping style inside documentation snippets.
- pr-feedback.yml counted format issues with
  `$(... | grep -c "would reformat" || echo "0")`. `grep -c` prints `0` *and*
  exits 1 when nothing matches, so the substitution became the literal "0\n0",
  which broke `$((total_issues + format_issues))` and the later `[ -gt ]`
  comparison ("Invalid format '0'"). Newer ruff also reports
  "N files would be reformatted", which the old pattern never matched. Now the
  summary line is parsed with a safe fallback.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔍 PR Analysis Results

PR: #48 | Commit: 482b3f04cc1139e6726a63618ff63cc6cf64c4bb

🎨 Code Formatting

✅ All files properly formatted

🔧 Code Linting

✅ No linting issues found

📝 Type Checking

❌ Type checking issues found

Click to see type issues
usage: mypy [-h] [-v] [-V] [more options; see below]
            [-m MODULE] [-p PACKAGE] [-c PROGRAM_TEXT] [files ...]
mypy: error: unrecognized arguments: --json-report mypy_report.json

Fix: Add proper type annotations and resolve type errors

🔒 Security Analysis

✅ No security issues detected

📊 Test Coverage Analysis

✅ Coverage 81.71641791044776% meets 80% requirement

🧹 Dead Code Analysis

✅ Dead code analysis completed

📋 Summary

⚠️ Found 1 issue(s) that should be addressed:

  • 📝 Types: Issues found

🔧 Quick Fix Commands:

# Fix formatting and auto-fixable linting issues
ruff format .
ruff check . --fix

# Run tests with coverage
pytest tests/unit/ --cov=markitdown_mcp --cov-report=term-missing

# Check security
bandit -r markitdown_mcp/

This analysis was automatically generated by the PR feedback workflow.
Report generated at 2026-08-24 21:45:36 UTC

@github-actions

Copy link
Copy Markdown
Contributor

🔍 CI Quality Gates Summary

Overall Status: ✅ All Passed

Check Status Details Action Required
🎨 Format ✅ Passed ruff format check None
🔧 Lint ✅ Passed ruff linting None
📝 Types ✅ Passed mypy type checking None
🧪 Tests ✅ Passed Unit tests None
📊 Coverage 81.7% Minimum: 80% None
🔌 MCP ✅ Valid Protocol compliance None
🔒 Security ✅ Clean Dependency audit None

🔗 Quick Links

🛠️ Quick Fix Commands

# Fix most issues automatically
ruff format .
ruff check . --fix

# Run tests locally
pytest tests/unit/ --cov=markitdown_mcp

# Check types
mypy markitdown_mcp

Last updated: 2026-08-24 21:46:17 UTC

@github-actions

Copy link
Copy Markdown
Contributor

🔍 PR Quality Summary

CI Status

✅ Security: success
✅ Docs: success
✅ Tests: success

Metrics

Metric Value Trend
📊 Coverage N/A -
🧪 Tests Test results unavailable -
⏱️ Performance No performance data -

Quality Checks

  • Format & Lint: Ruff formatting and linting
  • Type Safety: MyPy strict type checking
  • Security: Bandit, Safety, GitLeaks scanning
  • MCP Protocol: Tool schema validation
  • Documentation: Docstring coverage (80%+)

MCP Tools

  • convert_file - Convert individual files to Markdown
  • convert_directory - Batch convert directories
  • list_supported_formats - Query supported file types

🤖 Auto-generated by CI • Last updated: 2026-08-24 21:49 UTC

@trsdn

trsdn commented Aug 24, 2026

Copy link
Copy Markdown
Owner Author

CI ist grün (56 pass / 0 fail)

Nach dem Restore waren drei Checks rot — beide Ursachen sind vorbestehend auf main und haben nichts mit der Umbenennung zu tun. Sie sind erst jetzt aufgeschlagen, weil CI ein ungepinntes ruff>=0.1.0 installiert und eine neuere Version zwei Verhaltensänderungen mitbringt:

  1. Code Quality Checks — ruff formatiert inzwischen auch Python-Blöcke innerhalb von Markdown. Dadurch fiel AGENTS.md (von diesem PR nicht angefasst) durch ruff format --check. ruff format angewendet; der Diff sind reine Quote-/Umbruch-Änderungen in Doku-Snippets (+44/−53).

  2. Detect & Report Issues — echter Shell-Bug in pr-feedback.yml:

    format_issues=$(ruff format --check . 2>&1 | grep -c "would reformat" || echo "0")

    grep -c gibt bei 0 Treffern 0 aus und beendet sich mit Exit 1 — der || echo "0"-Fallback hängt also ein zweites 0 an, die Variable enthält den String "0\n0". Das erklärt exakt die Logs:

    line 29: 0
    0: syntax error in expression (error token is "0")
    line 215: [: 0
    0: integer expression expected
    ##[error]Invalid format '0'
    

    Zusätzlich meldet neueres ruff "N files would be reformatted", was das alte Muster "would reformat" gar nicht mehr trifft — der Zähler wäre also selbst ohne den Exit-Code-Bug immer 0 gewesen. Jetzt wird die Summary-Zeile geparst, mit Fallback. Lokal gegen Singular-, Plural- und Kein-Treffer-Ausgabe verifiziert.

  3. All CI Gates Passed war nur die Aggregation von (1).

Verifikation: ruff format --check . → 39 files already formatted · ruff check . → All checks passed · actionlint auf pr-feedback.yml → identische Befundzahl wie main (22, keine neuen).

Falls gewünscht, kann ruff in den Dev-Dependencies gepinnt werden, damit solche Toolchain-Updates nicht unangekündigt die Gates brechen — das habe ich hier bewusst nicht gemacht, um den PR nicht weiter auszudehnen.

@trsdn
trsdn merged commit 24b37ed into main Aug 24, 2026
62 checks passed
@trsdn
trsdn deleted the trsdn-pypi-trusted-publishing branch August 24, 2026 21:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant