Skip to content

Cross-reference linking parity for fastmcp & argparse components#57

Merged
tony merged 9 commits into
mainfrom
feat/56-component-linking-parity
Jun 16, 2026
Merged

Cross-reference linking parity for fastmcp & argparse components#57
tony merged 9 commits into
mainfrom
feat/56-component-linking-parity

Conversation

@tony

@tony tony commented Jun 16, 2026

Copy link
Copy Markdown
Member

Summary

  • Add {resource} / {resourceref} / {prompt} / {promptref} inline cross-reference roles to sphinx-autodoc-fastmcp, giving non-tool components the same prose-linking affordance tools already had via {tool} / {toolref}. {resource} resolves both fixed resources and resource templates; an unknown name degrades to a plain literal instead of a dangling link.
  • Add :no-index: support to the fastmcp-prompt, fastmcp-resource, and fastmcp-resource-template directives (previously tool-only), so a component shown on more than one page can keep a single canonical cross-reference home.
  • Add :no-index: to the argparse directive — the only auto-directive in the workspace that lacked it — suppressing argparse:* / std:cmdoption inventory targets on secondary appearances while keeping the rendered card and its HTML anchors intact.
  • Lock the existing (but previously untested) behavior that resource, prompt, and resource-template directives register canonical std:labels in objects.inv, closing issue sphinx-autodoc-fastmcp: {fastmcp-resource} and {fastmcp-resource-template} don't register cross-reference labels (no {ref}, absent from objects.inv) #56's acceptance criterion that resources be intersphinx-reachable.
  • Document the new roles and :no-index: flags in the fastmcp tutorial.

Closes #56.

Context

Issue #56 reported that fastmcp-resource / fastmcp-resource-template never register a std:label, leaving resources un-{ref}-able and absent from objects.inv. Investigation showed the load-bearing registration already happens inside the shared _build_resource_card() helper (and has since before the issue was filed) — a controlled build confirms fastmcp-resource-*, fastmcp-prompt-*, and fastmcp-resource-template-* labels land in objects.inv. What was genuinely missing were the issue's explicitly-optional follow-ons (a role family, :no-index: parity) and any regression test guarding the inventory behavior. This PR delivers those, plus the analogous :no-index: gap found in argparse during the cross-package audit.

Changes by area

sphinx-autodoc-fastmcp

  • _roles.py: kind-agnostic _component_ref_placeholder node + role factories for the four new roles, targeting the canonical fastmcp-<kind>-<slug> label (resources/prompts carry no bare-slug alias, unlike tools).
  • _transforms.py: resolve_component_refs (doctree-resolved) mirrors resolve_tool_refs without the safety-badge branches; {resource} tries both the resource and resource-template id families.
  • _directives.py: :no-index: option_spec + handling on the prompt/resource/template directives; threads no_index through _build_resource_card, marking the section fastmcp_no_index so the doctree-read pass mirrors the skip.
  • __init__.py: registers the four roles and connects the resolver.

sphinx-autodoc-argparse

  • directive.py: :no-index: option_spec; sets RenderConfig.register_xref_targets from it.
  • renderer.py: gates _register_argument / _register_program / _register_subcommand and the implicit section names on register_xref_targets, keeping per-section ids (anchors/TOC) intact.

Docs

  • tutorial.md: documents the component roles and the :no-index: flag.

Design decisions

  • Roles target the canonical id, not a bare slug: tools keep a bare-slug alias for back-compat with pre-kind-prefix URLs; resources/prompts have no such history, so the roles point straight at fastmcp-<kind>-<slug> with no alias-collision risk.
  • {resource} covers templates too: rather than a separate {resource-template} role, the resolver falls back from fastmcp-resource-<slug> to fastmcp-resource-template-<slug>, so one spelling links either.
  • No new CSS: the roles emit reference[literal] (like {toolref}), so no gp-sphinx-* class is introduced and the CSS self-containment rule is unaffected.
  • argparse :no-index: keeps anchors: only cross-reference targets (domain entries + implicit section labels) are suppressed; ids remain so the card still renders and links within the page.

Test plan

  • tests/ext/fastmcp/test_component_linking.py — component labels in objects.inv; role resolution incl. template fallback; unknown-name degradation; two-page :no-index: keeps a single canonical home with no duplicate-label warning
  • tests/ext/autodoc_argparse/test_no_index_integration.py:no-index: argparse block emits no argparse:* / std:cmdoption entries and an empty progoptions, while the card still renders
  • uv run ruff check . / uv run ruff format .
  • uv run mypy (full workspace)
  • uv run pytest (full suite)
  • just build-docs (clean build, no new warnings)

tony added 5 commits June 15, 2026 19:47
why: Resource, prompt, and resource-template directives already register
canonical std-domain labels, so they land in objects.inv and resolve via
{ref} — but no test guarded it. Issue #56's acceptance criterion #2 (resources
reachable via intersphinx as std:label) was unverified, leaving the behavior
free to regress silently.

what:
- Add tests/ext/fastmcp/test_component_linking.py with a synthetic project that
  documents one prompt, resource, and resource template
- Assert each canonical label lands in the built objects.inv as a std:label
…emplate

why: Only fastmcp-tool honored the standard :no-index: flag. A prompt, resource,
or resource template shown on more than one page had no way to opt a secondary
appearance out of label registration, so its canonical cross-reference home was
nondeterministic (last page read wins). Issue #56 requests this parity.

what:
- Add :no-index: to option_spec on FastMCPPromptDirective, FastMCPResourceDirective,
  and FastMCPResourceTemplateDirective
- Skip canonical label registration + note_explicit_target when set, marking the
  section fastmcp_no_index so the doctree-read pass mirrors the skip
- Thread no_index through the shared _build_resource_card helper
- Cover two-page no-index behavior in test_component_linking.py
why: Tools had {tool}/{toolref} inline cross-reference roles, but resources,
prompts, and resource templates had none — prose could only link them via the
verbose canonical {ref}`fastmcp-resource-<slug>`. Issue #56's follow-on asks
for the same chip affordance.

what:
- Add a kind-agnostic _component_ref_placeholder + role factories in _roles.py
  for {resource}/{resourceref}/{prompt}/{promptref}, targeting the canonical
  fastmcp-<kind>-<slug> label (resources/prompts carry no bare-slug alias)
- Add resolve_component_refs (doctree-resolved) mirroring resolve_tool_refs
  without the safety-badge branches; {resource} resolves against both the
  resource and resource-template id families; unresolved names degrade to a
  bare literal
- Register the four roles and connect the resolver in setup()
- Cover role resolution, template fallback, and graceful degradation in tests
…rgets

why: The argparse directive was the only auto-directive in the workspace without
:no-index:. A parser documented on more than one page registered its options,
program, and subcommands on every appearance, polluting objects.inv and the
per-domain index with duplicate cross-reference targets and giving the parser no
single canonical xref home.

what:
- Add :no-index: to ArgparseDirective.option_spec; set
  RenderConfig.register_xref_targets from it in _build_render_config
- Gate _register_argument / _register_program / _register_subcommand and the
  implicit section names on register_xref_targets, keeping HTML anchors intact
- Cover objects.inv suppression and intact rendering in a new integration test
why: The tutorial showed only the {tool}/{toolref} inline roles and never
mentioned that prompts, resources, and templates support :no-index:. Readers
had no pointer to the new cross-reference affordances.

what:
- Show {resource}/{resourceref}/{prompt}/{promptref} inline-role usage, noting
  {resource} resolves both fixed resources and resource templates
- Document the :no-index: flag for multi-page prompt/resource/template cards
@codecov-commenter

codecov-commenter commented Jun 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.43590% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.42%. Comparing base (90c5370) to head (ab55472).

Files with missing lines Patch % Lines
...-fastmcp/src/sphinx_autodoc_fastmcp/_transforms.py 90.00% 3 Missing ⚠️
...-fastmcp/src/sphinx_autodoc_fastmcp/_directives.py 91.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #57      +/-   ##
==========================================
+ Coverage   92.38%   92.42%   +0.03%     
==========================================
  Files         256      258       +2     
  Lines       20188    20334     +146     
==========================================
+ Hits        18651    18793     +142     
- Misses       1537     1541       +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tony

tony commented Jun 16, 2026

Copy link
Copy Markdown
Member Author

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

tony added 4 commits June 15, 2026 20:16
…string

why: The docstring said the resolver "targets the canonical fastmcp-<kind>-<slug>
id directly" — accurate for prompts but wrong for resources, where the resolver
tries fastmcp-resource-<slug> then fastmcp-resource-template-<slug>.

what:
- Describe the per-kind candidate families: prompt -> single canonical id;
  resource -> resource then resource-template, so one spelling links either
why: The :no-index: test hand-rolled a Sphinx() build and a private _Result
NamedTuple, while the sibling component-linking test added in the same work uses
tests/_sphinx_scenarios.py. Use the shared harness so both follow one pattern.

what:
- Replace _build/_Result/_purge_parser_module with SphinxScenario +
  build_shared_sphinx_result (module-scoped, purge_modules=("myparser",))
- Read objects.inv from result.outdir and the rendered card via read_output;
  assertions (no argparse:* / std:cmdoption, empty progoptions, card renders)
  are unchanged
…nfig field

why: The new _make_component_ref_role factory shipped without a doctest or NumPy
parameter docs, and RenderConfig's class doctest didn't assert the new
register_xref_targets default — gaps relative to the doctest-everything norm.

what:
- Add NumPy Parameters/Returns and a doctest to _make_component_ref_role
  asserting the placeholder's refkind/refslug/reftext
- Assert register_xref_targets in the RenderConfig class doctest
why: Record the forthcoming version's user-visible deliverables in the
0.0.1a31 unreleased block.

what:
- What's new: {resource}/{resourceref} and {prompt}/{promptref}
  cross-reference roles for sphinx-autodoc-fastmcp
- What's new: :no-index: on the fastmcp prompt/resource/resource-template
  and argparse directives
@tony tony merged commit 99f98a3 into main Jun 16, 2026
30 checks passed
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.

sphinx-autodoc-fastmcp: {fastmcp-resource} and {fastmcp-resource-template} don't register cross-reference labels (no {ref}, absent from objects.inv)

2 participants