@@ -713,6 +713,10 @@ def _register_tool_labels(app: Sphinx, doctree: nodes.document) -> None:
713713 StandardDomain. This hook mirrors the pattern used by
714714 ``sphinx.ext.autosectionlabel`` so that ``{ref}`list-sessions``` works
715715 from any page.
716+
717+ The primary label uses just the tool name (no safety badge) so that
718+ ``{ref}`` renders a clean ``tool_name`` link. Use ``{tool}`` role
719+ for a link that includes the colored safety badge.
716720 """
717721 domain = t .cast ("StandardDomain" , app .env .get_domain ("std" ))
718722 docname = app .env .docname
@@ -721,15 +725,92 @@ def _register_tool_labels(app: Sphinx, doctree: nodes.document) -> None:
721725 continue
722726 section_id = section ["ids" ][0 ]
723727 if section .children and isinstance (section [0 ], nodes .title ):
724- title = section [0 ].astext ()
728+ # Extract just the tool name from the first literal child,
729+ # ignoring the safety badge that follows it.
730+ title_node = section [0 ]
731+ tool_name = ""
732+ for child in title_node .children :
733+ if isinstance (child , nodes .literal ):
734+ tool_name = child .astext ()
735+ break
736+ if not tool_name :
737+ tool_name = title_node .astext ()
725738 domain .anonlabels [section_id ] = (docname , section_id )
726- domain .labels [section_id ] = (docname , section_id , title )
739+ domain .labels [section_id ] = (docname , section_id , tool_name )
740+
741+
742+ class _tool_ref_placeholder (nodes .General , nodes .Inline , nodes .Element ):
743+ """Placeholder node for ``{tool}`` role, resolved at doctree-resolved."""
744+
745+
746+ def _resolve_tool_refs (
747+ app : Sphinx ,
748+ doctree : nodes .document ,
749+ fromdocname : str ,
750+ ) -> None :
751+ """Resolve ``{tool}`` placeholders into links with safety badges.
752+
753+ Runs at ``doctree-resolved`` — after all labels are registered and
754+ standard ``{ref}`` resolution is done.
755+ """
756+ domain = t .cast ("StandardDomain" , app .env .get_domain ("std" ))
757+ builder = app .builder
758+ tool_data : dict [str , ToolInfo ] = getattr (app .env , "fastmcp_tools" , {})
759+
760+ for node in list (doctree .findall (_tool_ref_placeholder )):
761+ target = node .get ("reftarget" , "" )
762+ label_info = domain .labels .get (target )
763+ if label_info is None :
764+ node .replace_self (nodes .literal ("" , target .replace ("-" , "_" )))
765+ continue
766+
767+ todocname , labelid , _title = label_info
768+ tool_name = target .replace ("-" , "_" )
769+
770+ newnode = nodes .reference ("" , "" , internal = True )
771+ try :
772+ newnode ["refuri" ] = builder .get_relative_uri (fromdocname , todocname )
773+ if labelid :
774+ newnode ["refuri" ] += "#" + labelid
775+ except Exception :
776+ newnode ["refuri" ] = "#" + labelid
777+ newnode ["classes" ].append ("reference" )
778+ newnode ["classes" ].append ("internal" )
779+
780+ newnode += nodes .literal ("" , tool_name )
781+
782+ tool_info = tool_data .get (tool_name )
783+ if tool_info :
784+ newnode += nodes .Text (" " )
785+ newnode += _safety_badge (tool_info .safety )
786+
787+ node .replace_self (newnode )
788+
789+
790+ def _tool_role (
791+ name : str ,
792+ rawtext : str ,
793+ text : str ,
794+ lineno : int ,
795+ inliner : object ,
796+ options : dict [str , object ] | None = None ,
797+ content : list [str ] | None = None ,
798+ ) -> tuple [list [nodes .Node ], list [nodes .system_message ]]:
799+ """Inline role ``:tool:`capture-pane``` → linked tool name + safety badge.
800+
801+ Creates a placeholder node resolved later by ``_resolve_tool_refs``.
802+ """
803+ target = text .strip ().replace ("_" , "-" )
804+ node = _tool_ref_placeholder (rawtext , reftarget = target )
805+ return [node ], []
727806
728807
729808def setup (app : Sphinx ) -> ExtensionMetadata :
730809 """Register the fastmcp_autodoc extension."""
731810 app .connect ("builder-inited" , _collect_tools )
732811 app .connect ("doctree-read" , _register_tool_labels )
812+ app .connect ("doctree-resolved" , _resolve_tool_refs )
813+ app .add_role ("tool" , _tool_role )
733814 app .add_directive ("fastmcp-tool" , FastMCPToolDirective )
734815 app .add_directive ("fastmcp-tool-input" , FastMCPToolInputDirective )
735816 app .add_directive ("fastmcp-toolsummary" , FastMCPToolSummaryDirective )
0 commit comments