feat(utils/docs): add Markdown MCP docs generator (Docusaurus- and pdoc-compatible)#40
Conversation
…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 EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
🎉 Thanks for opening this pull request! Your contribution is appreciated. Here are some helpful commands you can use: Quick Commands
Available Poe TasksYou can run any of these tasks using the slash command: Core Tasks
Quick Fixes
Build & Install
Other Commands
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.
…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.
There was a problem hiding this comment.
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.docswithgenerate_markdown_docs()and a matchingpython -m fastmcp_extensions.utils.docsCLI. - Implements bucketing/rendering helpers to emit
index.mdplus one<module>.mdpermcp_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.
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.generate_markdown_docs(server_spec, output)— shells out tofastmcp inspect <server_spec>for the tools/resources/prompts JSON, imports the server module to readmcp_modulemetadata off_REGISTERED_PROMPTS/_REGISTERED_RESOURCESfor primitives that don't round-trip it through the MCP annotation schema, buckets everything bymcp_module, and emits one<module>.mdper bucket plus anindex.mdoverview.python -m fastmcp_extensions.utils.docs --server-spec <spec> --output <dir>— matches thetest_tool/describe_serversibling pattern.<a id>anchors so pdoc's TOC extractor produces clean sidebar entries) and Docusaurus-hostable (index.mdcarries YAML front-matter; body is plain CommonMark + GFM tables +<details>for collapsible JSON schemas; no MDX-only components).--output: refuses tormtreethe filesystem root, cwd,$HOME, or any ancestor of cwd/home (e.g.Path('..')).src/fastmcp_extensions/utils/__init__.py— re-exportsgenerate_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_moduleprecedence, 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 thenoqa: PLC2701comments are gone; (b) replacing the PyAirbyte-repo-specific_REPO_ROOTsafety check with a generic guard since this is now a library; (c) accepting dotted-module server specs in addition to file-path specs (withsrc.prefix stripping for src-layout projects).A follow-up PR on
airbyte-ops-mcp(#723) consumes this API fromdocs/generate.py, grafts each per-module.mdinto the correspondingairbyte_ops_mcp/mcp/<group>.pydocstring 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
_run_fastmcp_inspectnow captures stdout/stderr and re-raisessubprocess.CalledProcessErrorasRuntimeErrorwith diagnostic context, matching the public contract already used for missing-CLI / timeout failures._yaml_scalar()helper emits all three front-matter fields (title,sidebar_label,description) as double-quoted YAML scalars viajson.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 ofcwdor$HOME(enumeratesPath.parentsfor each), in addition to the existing//$CWD/$HOME/ filesystem-anchor checks../src/...path-normalization concerns: replied inline explaining why the current best-effort semantics are sufficient for the intended single-invocation callers (with gracefulMISC_MODULEfallback on edge cases).#### Parameters/#### Argumentswith**Parameters:**/**Arguments:**so they don't clutter pdoc's sidebar as single-child TOC entries under each tool/prompt.test_render_prompt_arguments_block_uses_bold_labelcovers the bold-label path for prompts;test_render_index_has_frontmatter_and_module_tableupdated to assert the new quoted front-matter form.Review & Testing Checklist for Human
fastmcp inspectagainst 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>.mdfiles there, or pull this branch and runpython -m fastmcp_extensions.utils.docs --server-spec <some real FastMCP server>:app --output /tmp/mcp-docslocally./,$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$CWDwith an explicit--forceescape hatch._REGISTERED_*private-name import. Still leading-underscore cross-module (but same package). The best-effortexcept Exceptionmeans shape drift silently falls back tomisc. Worth confirming whether we want to promote these to a documented internal helper in a follow-up.Suggested test plan:
Notes
Publish Docs, expected on PR branches).fastmcpis already a hard dep, which ships thefastmcp inspectCLI we shell out to.Link to Devin session: https://app.devin.ai/sessions/52a3cf7bc9084a39b7dfda021c4116d5
Requested by: Aaron ("AJ") Steers (@aaronsteers)