Skip to content

Commit 6c4d86f

Browse files
authored
Merge pull request #128 from jlowin/mount
Change default mounted tool separator from / to _
2 parents ea684ed + e5843f9 commit 6c4d86f

File tree

7 files changed

+50
-40
lines changed

7 files changed

+50
-40
lines changed

Diff for: examples/mount_example.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,11 @@ def check_app_status() -> dict[str, str]:
6464

6565
# Mount sub-applications
6666
app.mount("weather", weather_app)
67+
6768
app.mount("news", news_app)
6869

6970

70-
async def start_server():
71+
async def get_server_details():
7172
"""Print information about mounted resources."""
7273
# Print available tools
7374
tools = app._tool_manager.list_tools()
@@ -105,7 +106,7 @@ async def start_server():
105106

106107
if __name__ == "__main__":
107108
# First run our async function to display info
108-
asyncio.run(start_server())
109+
asyncio.run(get_server_details())
109110

110111
# Then start the server (uncomment to run the server)
111112
# app.run()

Diff for: src/fastmcp/cli/cli.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def _build_uv_command(
6565
"""Build the uv run command that runs a MCP server through mcp run."""
6666
cmd = ["uv"]
6767

68-
cmd.extend(["run", "--with", "mcp"])
68+
cmd.extend(["run", "--with", "fastmcp"])
6969

7070
if with_editable:
7171
cmd.extend(["--with-editable", str(with_editable)])
@@ -76,7 +76,7 @@ def _build_uv_command(
7676
cmd.extend(["--with", pkg])
7777

7878
# Add mcp run command
79-
cmd.extend(["mcp", "run", file_spec])
79+
cmd.extend(["fastmcp", "run", file_spec])
8080
return cmd
8181

8282

Diff for: src/fastmcp/prompts/prompt_manager.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,6 @@ def import_prompts(
8585

8686
new_prompt = prompt.copy(updates=dict(name=prefixed_name))
8787

88-
# Log the import
89-
logger.debug(f"Importing prompt with name {name} as {prefixed_name}")
90-
9188
# Store the prompt with the prefixed name
9289
self.add_prompt(new_prompt)
90+
logger.debug(f'Imported prompt "{name}" as "{prefixed_name}"')

Diff for: src/fastmcp/resources/resource_manager.py

+4-8
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,9 @@ def import_resources(
156156

157157
new_resource = resource.copy(updates=dict(uri=prefixed_uri))
158158

159-
# Log the import
160-
logger.debug(f"Importing resource with URI {uri} as {prefixed_uri}")
161-
162159
# Store directly in resources dictionary
163160
self.add_resource(new_resource)
161+
logger.debug(f'Imported resource "{uri}" as "{prefixed_uri}"')
164162

165163
def import_templates(
166164
self, manager: "ResourceManager", prefix: str | None = None
@@ -188,10 +186,8 @@ def import_templates(
188186
updates=dict(uri_template=prefixed_uri_template)
189187
)
190188

191-
# Log the import
192-
logger.debug(
193-
f"Importing resource template with URI {uri_template} as {prefixed_uri_template}"
194-
)
195-
196189
# Store directly in templates dictionary
197190
self.add_template(new_template)
191+
logger.debug(
192+
f'Imported template "{uri_template}" as "{prefixed_uri_template}"'
193+
)

Diff for: src/fastmcp/server/server.py

+28-13
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ def _setup_handlers(self) -> None:
153153

154154
async def list_tools(self) -> list[MCPTool]:
155155
"""List all available tools."""
156+
156157
tools = self._tool_manager.list_tools()
157158
return [
158159
MCPTool(
@@ -534,37 +535,51 @@ async def get_prompt(
534535
logger.error(f"Error getting prompt {name}: {e}")
535536
raise ValueError(str(e))
536537

537-
def mount(self, prefix: str, app: "FastMCP") -> None:
538+
def mount(
539+
self,
540+
prefix: str,
541+
app: "FastMCP",
542+
tool_separator: str | None = None,
543+
resource_separator: str | None = None,
544+
prompt_separator: str | None = None,
545+
) -> None:
538546
"""Mount another FastMCP application with a given prefix.
539547
540548
When an application is mounted:
541-
- The tools are imported with prefixed names
542-
Example: If app has a tool named "get_weather", it will be available as "weather/get_weather"
543-
- The resources are imported with prefixed URIs
549+
- The tools are imported with prefixed names using the tool_separator
550+
Example: If app has a tool named "get_weather", it will be available as "weatherget_weather"
551+
- The resources are imported with prefixed URIs using the resource_separator
544552
Example: If app has a resource with URI "weather://forecast", it will be available as "weather+weather://forecast"
545-
- The templates are imported with prefixed URI templates
553+
- The templates are imported with prefixed URI templates using the resource_separator
546554
Example: If app has a template with URI "weather://location/{id}", it will be available as "weather+weather://location/{id}"
547-
- The prompts are imported with prefixed names
548-
Example: If app has a prompt named "weather_prompt", it will be available as "weather/weather_prompt"
555+
- The prompts are imported with prefixed names using the prompt_separator
556+
Example: If app has a prompt named "weather_prompt", it will be available as "weather_weather_prompt"
549557
550558
Args:
551559
prefix: The prefix to use for the mounted application
552560
app: The FastMCP application to mount
553561
"""
562+
if tool_separator is None:
563+
tool_separator = "_"
564+
if resource_separator is None:
565+
resource_separator = "+"
566+
if prompt_separator is None:
567+
prompt_separator = "_"
568+
554569
# Mount the app in the list of mounted apps
555570
self._mounted_apps[prefix] = app
556571

557-
# Import tools from the mounted app with / delimiter
558-
tool_prefix = f"{prefix}/"
572+
# Import tools from the mounted app
573+
tool_prefix = f"{prefix}{tool_separator}"
559574
self._tool_manager.import_tools(app._tool_manager, tool_prefix)
560575

561-
# Import resources and templates from the mounted app with + delimiter
562-
resource_prefix = f"{prefix}+"
576+
# Import resources and templates from the mounted app
577+
resource_prefix = f"{prefix}{resource_separator}"
563578
self._resource_manager.import_resources(app._resource_manager, resource_prefix)
564579
self._resource_manager.import_templates(app._resource_manager, resource_prefix)
565580

566-
# Import prompts with / delimiter
567-
prompt_prefix = f"{prefix}/"
581+
# Import prompts from the mounted app
582+
prompt_prefix = f"{prefix}{prompt_separator}"
568583
self._prompt_manager.import_prompts(app._prompt_manager, prompt_prefix)
569584

570585
logger.info(f"Mounted app with prefix '{prefix}'")

Diff for: src/fastmcp/tools/tool_manager.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,4 @@ def import_tools(
9393
new_tool = tool.copy(updates=dict(name=prefixed_name))
9494
# Store the copied tool
9595
self.add_tool(new_tool)
96-
logger.debug(f"Imported tool: {name} as {prefixed_name}")
96+
logger.debug(f'Imported tool "{name}" as "{prefixed_name}"')

Diff for: tests/server/test_mount.py

+11-11
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ def sub_tool() -> str:
1616
main_app.mount("sub", sub_app)
1717

1818
# Verify the tool was imported with the prefix
19-
assert "sub/sub_tool" in main_app._tool_manager._tools
19+
assert "sub_sub_tool" in main_app._tool_manager._tools
2020
assert "sub_tool" in sub_app._tool_manager._tools
2121

2222
# Verify the original tool still exists in the sub-app
23-
tool = main_app._tool_manager._tools["sub/sub_tool"]
24-
assert tool.name == "sub/sub_tool"
23+
tool = main_app._tool_manager._tools["sub_sub_tool"]
24+
assert tool.name == "sub_sub_tool"
2525
assert callable(tool.fn)
2626

2727

@@ -46,8 +46,8 @@ def get_headlines() -> str:
4646
main_app.mount("news", news_app)
4747

4848
# Verify tools were imported with the correct prefixes
49-
assert "weather/get_forecast" in main_app._tool_manager._tools
50-
assert "news/get_headlines" in main_app._tool_manager._tools
49+
assert "weather_get_forecast" in main_app._tool_manager._tools
50+
assert "news_get_headlines" in main_app._tool_manager._tools
5151

5252

5353
async def test_mount_combines_tools():
@@ -68,16 +68,16 @@ def second_tool() -> str:
6868

6969
# Mount first app
7070
main_app.mount("api", first_app)
71-
assert "api/first_tool" in main_app._tool_manager._tools
71+
assert "api_first_tool" in main_app._tool_manager._tools
7272

7373
# Mount second app to same prefix
7474
main_app.mount("api", second_app)
7575

7676
# Verify second tool is there
77-
assert "api/second_tool" in main_app._tool_manager._tools
77+
assert "api_second_tool" in main_app._tool_manager._tools
7878

7979
# Tools from both mounts are combined
80-
assert "api/first_tool" in main_app._tool_manager._tools
80+
assert "api_first_tool" in main_app._tool_manager._tools
8181

8282

8383
async def test_mount_with_resources():
@@ -131,7 +131,7 @@ def greeting(name: str) -> str:
131131
main_app.mount("assistant", assistant_app)
132132

133133
# Verify the prompt was imported with the prefix
134-
assert "assistant/greeting" in main_app._prompt_manager._prompts
134+
assert "assistant_greeting" in main_app._prompt_manager._prompts
135135

136136

137137
async def test_mount_multiple_resource_templates():
@@ -180,5 +180,5 @@ def explain_sql(query: str) -> str:
180180
main_app.mount("sql", sql_app)
181181

182182
# Verify prompts were imported with correct prefixes
183-
assert "python/review_python" in main_app._prompt_manager._prompts
184-
assert "sql/explain_sql" in main_app._prompt_manager._prompts
183+
assert "python_review_python" in main_app._prompt_manager._prompts
184+
assert "sql_explain_sql" in main_app._prompt_manager._prompts

0 commit comments

Comments
 (0)