Skip to content

Add tool_names parameter to mount() for name overrides#2619

Merged
jlowin merged 2 commits intomainfrom
tool-name-overrides
Dec 15, 2025
Merged

Add tool_names parameter to mount() for name overrides#2619
jlowin merged 2 commits intomainfrom
tool-name-overrides

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Dec 15, 2025

When mounting servers with prefixes, auto-generated tool names can become unwieldy (e.g., api_v2_users_get_user). This adds a tool_names parameter to mount() that lets users override specific tool names:

main.mount(
    sub,
    prefix="api",
    tool_names={"get_user": "fetch_user"},  # override before prefixing
)

The override is applied instead of (not in addition to) the prefix, giving full control over the final name. The MCP SDK already warns for names > 128 chars (SEP-986), so no custom validation was added.

Closes #2596

@marvin-context-protocol marvin-context-protocol Bot added enhancement Improvement to existing functionality. For issues and smaller PR improvements. server Related to FastMCP server implementation or server-side functionality. labels Dec 15, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 15, 2025

Walkthrough

Adds an optional tool_names mapping to MountedServer and the mount API, propagating that mapping through mounting, registration, listing, and invocation paths. Observable tool/prompt/template keys are computed by checking tool_names overrides first, then applying a mount prefix, then falling back to the original key. Registration and nested mounting flows pass tool_names into _docket_lifespan and _register_mounted_server_functions. Call routing performs reverse-lookup against tool_names before applying standard prefix stripping.

Possibly related PRs

  • PR 2582: Modifies mount/import signatures and MountedServer handling in the server mounting code path, overlapping the mount API changes.
  • PR 2586: Adjusts nested-mounted server lookup, registration, and call routing, matching propagation and lookup alterations for mounted metadata.
  • PR 2575: Changes mounted-server naming and registration flow for function names during docket registration, aligning with tool name computation updates.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a tool_names parameter to mount() for name overrides.
Description check ✅ Passed The description provides clear context, a practical example, and references the closed issue, though it omits the Contributors Checklist items.
Linked Issues check ✅ Passed The PR addresses #2596 by providing a tool_names parameter for name overrides, allowing developers to manage tool naming independently of prefixes.
Out of Scope Changes check ✅ Passed All changes focus on implementing the tool_names feature for mounted servers with proper propagation through mounting flows.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch tool-name-overrides

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/fastmcp/server/server.py (1)

509-572: Docket registration for mounted tools ignores tool_names overrides, breaking task execution

_register_mounted_server_functions derives fn_name solely from prefix and tool.key (line 524), while both get_tools (line 940-941) and _list_tools (line 1226-1227) check for mounted.tool_names overrides. When a mounted tool has a tool_names override, the registered Docket function name will use the original prefixed key, but handle_tool_as_task retrieves the tool via get_tool() (which applies the override), then calls docket.add(tool.key) with the overridden key. This causes Docket to look for a function named by the override, but the registered function has the original prefixed name—task execution will fail.

Pass the full MountedServer (including tool_names) into _register_mounted_server_functions and apply the same override logic used in get_tools and _list_tools when deriving fn_name for tools.

🧹 Nitpick comments (4)
src/fastmcp/server/server.py (4)

1217-1237: Consistent override behavior in _list_tools, consider de‑duplicating key logic

The same tool_namesprefix → original key precedence as in get_tools is correctly applied here, and updating tool.key only when it changes keeps things tidy. To avoid future drift, you might consider extracting a small helper like _mounted_tool_key(mounted, original_key) and using it in both get_tools and _list_tools.


1646-1666: Reverse lookup for overridden tool names in _call_tool is sound; possible micro‑optimization

The reverse mapping from tool_names override back to the original key, with prefix fallback in the else block, is logically correct and keeps compatibility with both overrides and the existing prefix_ scheme.

If tool_names mappings ever become large or _call_tool is extremely hot, you could precompute a reverse dict on MountedServer (e.g. overrides_by_value) instead of scanning tool_names.items() on every call, but that’s an optional micro‑optimization.


2676-2751: mount(..., tool_names=...) API extension is coherent; consider doc tweak

The new tool_names: dict[str, str] | None parameter and its propagation into MountedServer give callers precise control over mounted tool names, and the docstring correctly describes the mapping as original‑name → custom‑name.

Since the earlier bullets describe prefix behavior unconditionally, you might consider clarifying that those examples describe the default behavior when tool_names is not used, and that overrides take precedence over prefixing on a per‑tool basis. That would make the interaction between prefix and tool_names explicit for readers of the docstring.


3005-3010: MountedServer.tool_names field is well‑typed; potential place for a reverse map

Adding tool_names: dict[str, str] | None = None here is consistent with its use sites and satisfies the full‑annotation guideline. If you later decide to optimize _call_tool lookups, this dataclass would also be a natural place to attach a precomputed reverse mapping (override → original) derived from tool_names.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c91c43e and 9fcf17f.

⛔ Files ignored due to path filters (1)
  • tests/server/test_mount.py is excluded by none and included by none
📒 Files selected for processing (1)
  • src/fastmcp/server/server.py (8 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Write Python code with Python ≥3.10 and include full type annotations
Use specific exception types in error handling - never use bare except
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns even if they're shorter

Files:

  • src/fastmcp/server/server.py
🧬 Code graph analysis (1)
src/fastmcp/server/server.py (3)
src/fastmcp/resources/resource.py (1)
  • key (149-156)
src/fastmcp/resources/template.py (1)
  • key (221-228)
src/fastmcp/utilities/components.py (1)
  • key (66-73)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.13 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.10 on windows-latest
  • GitHub Check: Run tests with lowest-direct dependencies
🔇 Additional comments (2)
src/fastmcp/server/server.py (2)

935-947: Override vs prefix resolution in get_tools looks correct

The precedence tool_namesprefix → original key is clear and matches the intended semantics, and model_copy(key=new_key) keeps the tool’s key consistent with the dict key. This hunk looks good.


1495-1512: Prompt key prefixing refactor is behavior‑preserving

The explicit if mounted.prefix: key = ... else: key = prompt.key plus model_copy only when the key changes is equivalent to the previous behavior and keeps the intent clearer without affecting behavior.

@jlowin jlowin merged commit b8ae95a into main Dec 15, 2025
10 of 11 checks passed
@jlowin jlowin deleted the tool-name-overrides branch December 15, 2025 01:56
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/fastmcp/server/server.py (2)

2686-2739: Clarify the tool_names mapping direction in documentation.

The documentation states "Keys are the original tool names from the mounted server" but doesn't explicitly clarify that values are the new/override names. Consider adding an example for clarity.

Apply this diff to improve the documentation:

-            tool_names: Optional mapping of original tool names to custom names. Use this
-                to override prefixed names. Keys are the original tool names from the
-                mounted server.
+            tool_names: Optional mapping of original tool names to custom names. Use this
+                to override prefixed names. Keys are the original tool names from the
+                mounted server, and values are the custom names to use instead of the
+                prefix-based names. Example: {"get_user": "fetch_user"} renames
+                "get_user" to "fetch_user" (without applying the prefix).

2686-2761: Consider: Add validation for tool_names mappings.

The current implementation doesn't validate that:

  1. Keys in tool_names correspond to actual tools in the mounted server
  2. Values in tool_names don't collide with existing tool names
  3. Override names are valid identifiers

While the PR description mentions length validation was intentionally omitted (relying on MCP SDK warnings), consider if basic validation would improve the developer experience:

def mount(
    self,
    server: FastMCP[LifespanResultT],
    prefix: str | None = None,
    as_proxy: bool | None = None,
    tool_names: dict[str, str] | None = None,
) -> None:
    """..."""
    from fastmcp.server.proxy import FastMCPProxy
    
    # Validate tool_names if provided
    if tool_names:
        if not all(isinstance(k, str) and isinstance(v, str) for k, v in tool_names.items()):
            raise ValueError("tool_names must be a dict[str, str]")
        if not all(v.strip() for v in tool_names.values()):
            raise ValueError("tool_names override values cannot be empty strings")
    
    # ... rest of implementation

This would catch obvious mistakes at mount time rather than at runtime when a tool is called.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9fcf17f and a8ab305.

📒 Files selected for processing (1)
  • src/fastmcp/server/server.py (12 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Write Python code with Python ≥3.10 and include full type annotations
Use specific exception types in error handling - never use bare except
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns even if they're shorter

Files:

  • src/fastmcp/server/server.py
🧬 Code graph analysis (1)
src/fastmcp/server/server.py (4)
src/fastmcp/tools/tool.py (1)
  • FunctionTool (275-419)
src/fastmcp/resources/resource.py (1)
  • key (149-156)
src/fastmcp/resources/template.py (1)
  • key (221-228)
src/fastmcp/utilities/components.py (1)
  • key (66-73)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run tests: Python 3.10 on windows-latest
  • GitHub Check: Run tests: Python 3.13 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
  • GitHub Check: Run tests with lowest-direct dependencies
🔇 Additional comments (6)
src/fastmcp/server/server.py (6)

3019-3019: LGTM!

The tool_names field is properly typed and initialized with a sensible default.


466-469: LGTM!

Correctly propagates tool_names to the registration function for task execution support.


509-582: Verify: Should tool_names apply only to tools, not prompts/resources/templates?

The tool_names override is applied to tools (lines 531-537) but not to prompts (line 547), resources (line 559), or templates (line 569), which only use prefix-based naming. This seems intentional based on the parameter name, but it creates an asymmetry in the API.

Consider:

  1. Is this the intended behavior?
  2. Should the documentation in mount() clarify that tool_names only affects tools?
  3. Would users expect similar override capability for prompts?

941-965: LGTM!

The tool naming logic correctly applies overrides before prefixes and properly creates copies to avoid mutating the original tools.


1209-1258: LGTM!

The implementation correctly applies tool_names overrides and includes a nice optimization to only create a copy when the key actually changes.


1647-1702: Verify: Potential name collisions between overrides and prefixed names.

The reverse lookup logic correctly handles tool_names overrides (lines 1660-1671), but consider this scenario:

  • Server A mounted with prefix="api", has tool "get_user" → exposed as "api_get_user"
  • Server B mounted with tool_names={"foo": "api_get_user"} → tool "foo" exposed as "api_get_user"

Both servers now expose a tool named "api_get_user". The reverse iteration order (line 1657) means "last mounted wins," which is consistent with the existing behavior for non-prefixed mounts. However, this could be confusing for users.

Consider:

  1. Should the documentation warn about potential collisions?
  2. Should mount() validate that override names don't collide with existing tool names?

For validation, you could add a check after line 2761:

# Optional: Validate no collisions with existing tools
if tool_names:
    existing_keys = set(await self.get_tools())
    collisions = set(tool_names.values()) & existing_keys
    if collisions:
        logger.warning(f"Tool name overrides {collisions} collide with existing tool names")

Note: This would require making mount() async or deferring validation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improvement to existing functionality. For issues and smaller PR improvements. server Related to FastMCP server implementation or server-side functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The prefix when composing multiple FastMCP servers bypasses the 56 character limit and doesn't get truncated

1 participant