Skip to content

feat(utils/docs): add Markdown MCP docs generator (Docusaurus- and pdoc-compatible)#40

Merged
Aaron ("AJ") Steers (aaronsteers) merged 4 commits into
mainfrom
devin/1776438760-mcp-markdown-docs
Apr 17, 2026
Merged

feat(utils/docs): add Markdown MCP docs generator (Docusaurus- and pdoc-compatible)#40
Aaron ("AJ") Steers (aaronsteers) merged 4 commits into
mainfrom
devin/1776438760-mcp-markdown-docs

Conversation

@aaronsteers

@aaronsteers Aaron ("AJ") Steers (aaronsteers) commented Apr 17, 2026

Copy link
Copy Markdown
Member

Summary

Vendors the MCP Markdown docs generator prototyped in airbytehq/PyAirbyte#1015 into this repo as a first-party public API, so downstream MCP servers (PyAirbyte, airbyte-ops-mcp, etc.) can generate Markdown docs for their server without carrying an inline copy of the generator script.

New in this PR:

  • src/fastmcp_extensions/utils/docs.py — the generator.
    • Public entry point: generate_markdown_docs(server_spec, output) — shells out to fastmcp inspect <server_spec> for the tools/resources/prompts JSON, imports the server module to read mcp_module metadata off _REGISTERED_PROMPTS / _REGISTERED_RESOURCES for primitives that don't round-trip it through the MCP annotation schema, buckets everything by mcp_module, and emits one <module>.md per bucket plus an index.md overview.
    • CLI entry point: python -m fastmcp_extensions.utils.docs --server-spec <spec> --output <dir> — matches the test_tool / describe_server sibling pattern.
    • Output format: pdoc-includable (no front-matter on per-module pages, plain-text headings with <a id> anchors so pdoc's TOC extractor produces clean sidebar entries) and Docusaurus-hostable (index.md carries YAML front-matter; body is plain CommonMark + GFM tables + <details> for collapsible JSON schemas; no MDX-only components).
    • "Parameters" (tools) and "Arguments" (prompts) render as bold labels rather than H4 headings, so pdoc's TOC extractor doesn't surface them as redundant single-child siblings of each tool/prompt in the left sidebar.
    • Safety guard on --output: refuses to rmtree the filesystem root, cwd, $HOME, or any ancestor of cwd/home (e.g. Path('..')).
  • src/fastmcp_extensions/utils/__init__.py — re-exports generate_markdown_docs.
  • tests/test_docs.py — 29 unit tests covering rendering/bucketing helpers (type formatting, default formatting, hint-badge rendering, parameters-table pipe escaping, anchor + heading structure, bold-label rendering for Parameters/Arguments, mcp_module precedence, alpha-sort + misc-pinned-last bucketing, quoted front-matter on index, output-dir safety).

The rendering logic is ~1:1 with airbytehq/PyAirbyte#1015's scripts/generate_mcp_markdown.py — the main deltas are: (a) moving it inside this package means the _REGISTERED_* access is no longer cross-package, so the noqa: PLC2701 comments are gone; (b) replacing the PyAirbyte-repo-specific _REPO_ROOT safety check with a generic guard since this is now a library; (c) accepting dotted-module server specs in addition to file-path specs (with src. prefix stripping for src-layout projects).

A follow-up PR on airbyte-ops-mcp (#723) consumes this API from docs/generate.py, grafts each per-module .md into the corresponding airbyte_ops_mcp/mcp/<group>.py docstring via pdoc's .. include:: directive, and adds __all__ = [] to hide the Python-level API docs — following the same pattern we established for CLI docs in airbyte-ops-mcp#717.

Updates since last revision

  • Copilot review feedback addressed (5 comments, all replied inline):
    • _run_fastmcp_inspect now captures stdout/stderr and re-raises subprocess.CalledProcessError as RuntimeError with diagnostic context, matching the public contract already used for missing-CLI / timeout failures.
    • New _yaml_scalar() helper emits all three front-matter fields (title, sidebar_label, description) as double-quoted YAML scalars via json.dumps, so server names containing YAML-significant characters (:, #, leading -/?) don't break downstream parsers.
    • _prepare_output_dir() now also refuses any path that is an ancestor of cwd or $HOME (enumerates Path.parents for each), in addition to the existing / / $CWD / $HOME / filesystem-anchor checks.
    • Process-global-registry and ./src/... path-normalization concerns: replied inline explaining why the current best-effort semantics are sufficient for the intended single-invocation callers (with graceful MISC_MODULE fallback on edge cases).
  • Bold Parameters/Arguments labels (per AJ's review): replaced #### Parameters / #### Arguments with **Parameters:** / **Arguments:** so they don't clutter pdoc's sidebar as single-child TOC entries under each tool/prompt.
  • New test test_render_prompt_arguments_block_uses_bold_label covers the bold-label path for prompts; test_render_index_has_frontmatter_and_module_table updated to assert the new quoted front-matter form.

Review & Testing Checklist for Human

  • End-to-end against a real server. The tests in this PR use synthetic fixtures — none of them invoke fastmcp inspect against a real server. Airbyte-ops-mcp #723 exercises the full path for real (21 module buckets, 52 tools, 1 resource, 1 prompt) — spot-check a couple of generated <module>.md files there, or pull this branch and run python -m fastmcp_extensions.utils.docs --server-spec <some real FastMCP server>:app --output /tmp/mcp-docs locally.
  • Output-dir safety guard. Now refuses /, $HOME, $CWD, and any ancestor of those — confirm this is the right shape for a public library API, or whether we want to require output to be under $CWD with an explicit --force escape hatch.
  • _REGISTERED_* private-name import. Still leading-underscore cross-module (but same package). The best-effort except Exception means shape drift silently falls back to misc. Worth confirming whether we want to promote these to a documented internal helper in a follow-up.

Suggested test plan:

git fetch origin devin/1776438760-mcp-markdown-docs
git checkout devin/1776438760-mcp-markdown-docs
uv sync --extra dev
uv run pytest tests/test_docs.py -v
# End-to-end smoke (requires a FastMCP server on PATH):
python -m fastmcp_extensions.utils.docs \
    --server-spec path/to/your_server.py:app \
    --output /tmp/mcp-docs
ls /tmp/mcp-docs/              # should show index.md + one <module>.md per bucket

Notes

  • 29 new unit tests, all passing locally; full repo CI 14/14 green (1 skipped is Publish Docs, expected on PR branches).
  • No new runtime dependencies — fastmcp is already a hard dep, which ships the fastmcp inspect CLI we shell out to.
  • Library-internal; no runtime behavior of existing tools/decorators changes.

Link to Devin session: https://app.devin.ai/sessions/52a3cf7bc9084a39b7dfda021c4116d5
Requested by: Aaron ("AJ") Steers (@aaronsteers)

…oc-compatible)

Vendors the MCP markdown generator from airbytehq/PyAirbyte#1015 as a first-party

public API under `fastmcp_extensions.utils.docs`, exposing a `generate_markdown_docs`

function and a CLI entry point (`python -m fastmcp_extensions.utils.docs`). This lets

consumers (PyAirbyte, airbyte-ops-mcp, ...) generate Markdown docs for their MCP

server without carrying an inline copy of the generator script.

Moving the generator into this package lets us drop the `noqa: PLC2701` that

cross-package access to `_REGISTERED_*` required, because it is no longer a

private-name access across packages — it is an internal reference.

Link to Devin session: https://app.devin.ai/sessions/52a3cf7bc9084a39b7dfda021c4116d5
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@github-actions

Copy link
Copy Markdown

🎉 Thanks for opening this pull request!

Your contribution is appreciated. Here are some helpful commands you can use:

Quick Commands

  • /autofix - Auto-format and fix linting issues (ruff format + ruff check --fix)
  • /lock - Update the uv.lock file with latest dependencies

Available Poe Tasks

You can run any of these tasks using the slash command: /poe <task-name>

Core Tasks

  • /poe test - Run all tests
  • /poe test-fast - Run tests with fast exit on first failure
  • /poe lint - Check code style and quality
  • /poe format - Format code with ruff
  • /poe deps - Check for unused and missing dependencies
  • /poe check - Run format check, linting, dependency check, and tests

Quick Fixes

  • /poe fix - Auto-format and fix linting issues
  • /poe clean - Clean up build artifacts and cache

Build & Install

  • /poe build - Build the package
  • /poe install - Install with development dependencies

Other Commands

  • /poe version - Show package version
  • /poe pre-commit - Run pre-commit style checks

The CI will automatically run tests when you push commits. Happy coding! 🚀

…-layout projects

Editable installs of `src/pkg/...` layouts expose `pkg` as importable, not

`src.pkg`, so naive path-to-dotted conversion of `src/pkg/mcp/server.py` would

yield an unimportable `src.pkg.mcp.server` and the best-effort import used to

resolve `mcp_module` on prompts/resources would silently fall back to `misc`.

Also adds a test covering the src-layout normalization.
@aaronsteers Aaron ("AJ") Steers (aaronsteers) marked this pull request as ready for review April 17, 2026 15:53
Copilot AI review requested due to automatic review settings April 17, 2026 15:53
…instead of H4 headings

pdoc's TOC extractor surfaces every heading at the configured depth
as a sibling nav entry. Since each tool has exactly one 'Parameters'
subsection (and each prompt at most one 'Arguments' subsection), the
prior H4 emitted a redundant left-sidebar row underneath the tool's
own entry with no navigation value.

Demote both labels to bold text so the sidebar shows tools / prompts
directly under their module's "Tools" / "Prompts" H2 with no
intermediate "Parameters" rung. Update the tool-rendering test to
assert on the new bold form and add a matching test for prompts.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a first-party Markdown documentation generator to fastmcp_extensions so downstream MCP servers can generate pdoc- and Docusaurus-compatible docs from fastmcp inspect output without vendoring a script.

Changes:

  • Introduces fastmcp_extensions.utils.docs with generate_markdown_docs() and a matching python -m fastmcp_extensions.utils.docs CLI.
  • Implements bucketing/rendering helpers to emit index.md plus one <module>.md per mcp_module, including anchors, tables, and collapsible JSON schema blocks.
  • Adds a dedicated unit test suite covering rendering, bucketing, and output-directory safety checks.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
src/fastmcp_extensions/utils/docs.py New Markdown docs generator (inspect runner, module bucketing, Markdown rendering, output dir management, CLI entrypoint).
src/fastmcp_extensions/utils/__init__.py Re-exports generate_markdown_docs from the utils package.
tests/test_docs.py Adds unit tests for docs generator helpers and output-dir safety behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/fastmcp_extensions/utils/docs.py
Comment thread src/fastmcp_extensions/utils/docs.py
Comment thread src/fastmcp_extensions/utils/docs.py
Comment thread src/fastmcp_extensions/utils/docs.py Outdated
Comment thread src/fastmcp_extensions/utils/docs.py
@aaronsteers Aaron ("AJ") Steers (aaronsteers) merged commit 35308c5 into main Apr 17, 2026
16 checks passed
@aaronsteers Aaron ("AJ") Steers (aaronsteers) deleted the devin/1776438760-mcp-markdown-docs branch April 17, 2026 16:14
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.

2 participants