Skip to content

Commit f786bc4

Browse files
committed
Auto generate names
1 parent 9822024 commit f786bc4

File tree

6 files changed

+75
-124
lines changed

6 files changed

+75
-124
lines changed

src/fastmcp/client/client.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,7 @@ def __init__(
225225
client_info: mcp.types.Implementation | None = None,
226226
auth: httpx.Auth | Literal["oauth"] | str | None = None,
227227
) -> None:
228-
# Generate random ID if no name provided
229-
if name is None:
230-
name = f"FastMCP-Client-{secrets.token_hex(4)}"
231-
self.name = name
228+
self.name = name or self.generate_name()
232229

233230
self.transport = cast(ClientTransportT, infer_transport(transport))
234231
if auth is not None:
@@ -346,6 +343,8 @@ def new(self) -> Client[ClientTransportT]:
346343
# Reset session state to fresh state
347344
new_client._session_state = ClientSessionState()
348345

346+
new_client.name += f"-{secrets.token_hex(2)}"
347+
349348
return new_client
350349

351350
@asynccontextmanager
@@ -545,6 +544,8 @@ async def list_resources_mcp(self) -> mcp.types.ListResourcesResult:
545544
Raises:
546545
RuntimeError: If called while the client is not connected.
547546
"""
547+
logger.debug(f"[{self.name}] called list_resources")
548+
548549
result = await self.session.list_resources()
549550
return result
550551

@@ -572,6 +573,8 @@ async def list_resource_templates_mcp(
572573
Raises:
573574
RuntimeError: If called while the client is not connected.
574575
"""
576+
logger.debug(f"[{self.name}] called list_resource_templates")
577+
575578
result = await self.session.list_resource_templates()
576579
return result
577580

@@ -604,6 +607,8 @@ async def read_resource_mcp(
604607
Raises:
605608
RuntimeError: If called while the client is not connected.
606609
"""
610+
logger.debug(f"[{self.name}] called read_resource: {uri}")
611+
607612
if isinstance(uri, str):
608613
uri = AnyUrl(uri) # Ensure AnyUrl
609614
result = await self.session.read_resource(uri)
@@ -658,6 +663,8 @@ async def list_prompts_mcp(self) -> mcp.types.ListPromptsResult:
658663
Raises:
659664
RuntimeError: If called while the client is not connected.
660665
"""
666+
logger.debug(f"[{self.name}] called list_prompts")
667+
661668
result = await self.session.list_prompts()
662669
return result
663670

@@ -690,6 +697,8 @@ async def get_prompt_mcp(
690697
Raises:
691698
RuntimeError: If called while the client is not connected.
692699
"""
700+
logger.debug(f"[{self.name}] called get_prompt: {name}")
701+
693702
# Serialize arguments for MCP protocol - convert non-string values to JSON
694703
serialized_arguments: dict[str, str] | None = None
695704
if arguments:
@@ -747,6 +756,8 @@ async def complete_mcp(
747756
Raises:
748757
RuntimeError: If called while the client is not connected.
749758
"""
759+
logger.debug(f"[{self.name}] called complete: {ref}")
760+
750761
result = await self.session.complete(ref=ref, argument=argument)
751762
return result
752763

@@ -782,6 +793,8 @@ async def list_tools_mcp(self) -> mcp.types.ListToolsResult:
782793
Raises:
783794
RuntimeError: If called while the client is not connected.
784795
"""
796+
logger.debug(f"[{self.name}] called list_tools")
797+
785798
result = await self.session.list_tools()
786799
return result
787800

@@ -824,6 +837,7 @@ async def call_tool_mcp(
824837
Raises:
825838
RuntimeError: If called while the client is not connected.
826839
"""
840+
logger.debug(f"[{self.name}] called call_tool: {name}")
827841

828842
if isinstance(timeout, int | float):
829843
timeout = datetime.timedelta(seconds=float(timeout))
@@ -905,6 +919,14 @@ async def call_tool(
905919
is_error=result.isError,
906920
)
907921

922+
@classmethod
923+
def generate_name(cls, name: str | None = None) -> str:
924+
class_name = cls.__name__
925+
if name is None:
926+
return f"{class_name}-{secrets.token_hex(2)}"
927+
else:
928+
return f"{class_name}-{name}-{secrets.token_hex(2)}"
929+
908930

909931
@dataclass
910932
class CallToolResult:

src/fastmcp/client/transports.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import contextlib
44
import datetime
55
import os
6+
import secrets
67
import shutil
78
import sys
89
import warnings
@@ -902,7 +903,8 @@ def __init__(self, config: MCPConfig | dict, name_as_prefix: bool = True):
902903

903904
# otherwise create a composite client
904905
else:
905-
self._composite_server = FastMCP[Any]()
906+
name = FastMCP.generate_name("MCPRouter")
907+
self._composite_server = FastMCP[Any](name=name)
906908

907909
for name, server, transport in mcp_config_to_servers_and_transports(
908910
self.config

src/fastmcp/mcp_config.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,20 @@ class _TransformingMCPServerMixin(FastMCPBaseModel):
9191

9292
def _to_server_and_underlying_transport(
9393
self,
94+
server_name: str | None = None,
95+
client_name: str | None = None,
9496
) -> tuple[FastMCP[Any], ClientTransport]:
9597
"""Turn the Transforming MCPServer into a FastMCP Server and also return the underlying transport."""
9698
from fastmcp import FastMCP
99+
from fastmcp.client import Client
97100

98-
transport: ClientTransport = super().to_transport() # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownVariableType]
101+
transport: ClientTransport = self.to_transport()
102+
103+
client: Client[ClientTransport] = Client(transport=transport, name=client_name)
99104

100105
wrapped_mcp_server = FastMCP.as_proxy(
101-
transport,
106+
name=server_name,
107+
backend=client,
102108
tool_transformations=self.tools,
103109
include_tags=self.include_tags,
104110
exclude_tags=self.exclude_tags,

src/fastmcp/server/proxy.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import inspect
4+
import secrets
45
import warnings
56
from collections.abc import Awaitable, Callable
67
from pathlib import Path
@@ -482,6 +483,9 @@ def __init__(
482483

483484
super().__init__(**kwargs)
484485

486+
if "name" not in kwargs:
487+
kwargs["name"] = self.generate_name()
488+
485489
# Handle client and client_factory parameters
486490
if client is not None and client_factory is not None:
487491
raise ValueError("Cannot specify both 'client' and 'client_factory'")
@@ -546,6 +550,9 @@ def __init__(
546550
| str,
547551
**kwargs,
548552
):
553+
554+
if "name" not in kwargs:
555+
kwargs["name"] = self.generate_name()
549556
if "roots" not in kwargs:
550557
kwargs["roots"] = default_proxy_roots_handler
551558
if "sampling_handler" not in kwargs:

src/fastmcp/server/server.py

Lines changed: 8 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,8 @@ def __init__(
199199
else:
200200
self._has_lifespan = True
201201
# Generate random ID if no name provided
202-
if name is None:
203-
name = f"FastMCP-{secrets.token_hex(4)}"
204202
self._mcp_server = LowLevelServer[LifespanResultT](
205-
name=name,
203+
name=name or self.generate_name(),
206204
version=version,
207205
instructions=instructions,
208206
lifespan=_lifespan_wrapper(self, lifespan),
@@ -2205,118 +2203,14 @@ def _should_enable_component(
22052203

22062204
return True
22072205

2208-
def generate_hierarchy_diagram(self, format: Literal["mermaid"] = "mermaid") -> str:
2209-
"""Generate a diagram showing the hierarchy of servers, mounted servers, proxies, clients and transports.
2210-
2211-
Args:
2212-
format: Output format, currently only "mermaid" is supported
2213-
2214-
Returns:
2215-
A string containing the diagram in the requested format
2216-
2217-
Example:
2218-
```python
2219-
server = FastMCP("MyServer")
2220-
print(server.generate_hierarchy_diagram())
2221-
```
2222-
"""
2223-
if format != "mermaid":
2224-
raise ValueError("Only 'mermaid' format is currently supported")
2225-
2226-
def get_server_type(server: FastMCP[Any]) -> str:
2227-
"""Determine the type of server for display"""
2228-
from fastmcp.server.proxy import FastMCPProxy
2229-
2230-
if isinstance(server, FastMCPProxy):
2231-
return "Proxy"
2232-
return "Server"
2233-
2234-
lines = ["graph TD"]
2235-
node_id = 0
2236-
2237-
def add_node(name: str, node_type: str = "Server") -> str:
2238-
"""Add a node and return its ID"""
2239-
nonlocal node_id
2240-
current_id = f"N{node_id}"
2241-
node_id += 1
2242-
2243-
# Choose appropriate mermaid shape based on type
2244-
if node_type == "Proxy":
2245-
shape = f'{current_id}[["{name}<br/>({node_type})"]'
2246-
elif node_type == "Client":
2247-
shape = f'{current_id}({{"{name}<br/>({node_type})"}})'
2248-
elif node_type == "Transport":
2249-
shape = f'{current_id}[["{name}<br/>({node_type})"]'
2250-
else: # Server
2251-
shape = f'{current_id}["{name}<br/>({node_type})"]'
2252-
2253-
lines.append(f" {shape}")
2254-
return current_id
2255-
2256-
def add_connection(from_id: str, to_id: str, label: str = "") -> None:
2257-
"""Add a connection between nodes"""
2258-
if label:
2259-
lines.append(f" {from_id} -->|{label}| {to_id}")
2260-
else:
2261-
lines.append(f" {from_id} --> {to_id}")
2262-
2263-
# Add the main server
2264-
main_server_id = add_node(self.name, get_server_type(self))
2265-
2266-
# Add mounted servers recursively
2267-
def process_server(server: FastMCP[Any], parent_id: str) -> None:
2268-
for mounted in server._mounted_servers:
2269-
server_type = get_server_type(mounted.server)
2270-
mounted_id = add_node(mounted.server.name, server_type)
2271-
2272-
# Add connection with prefix label if it exists
2273-
prefix_label = (
2274-
f"prefix: {mounted.prefix}" if mounted.prefix else "no prefix"
2275-
)
2276-
add_connection(parent_id, mounted_id, prefix_label)
2277-
2278-
# Recursively process this mounted server's mounts
2279-
process_server(mounted.server, mounted_id)
2280-
2281-
# If this is a proxy, try to show its client info
2282-
from fastmcp.server.proxy import FastMCPProxy
2283-
2284-
if isinstance(mounted.server, FastMCPProxy):
2285-
try:
2286-
# Add a representation of the proxy's client factory
2287-
client_id = add_node("Client Factory", "Client")
2288-
add_connection(mounted_id, client_id, "uses")
2289-
except Exception:
2290-
# In case of any issues accessing proxy internals, skip
2291-
pass
2292-
2293-
# Process all mounted servers
2294-
process_server(self, main_server_id)
2295-
2296-
# If this is a proxy server, show its client connection
2297-
from fastmcp.server.proxy import FastMCPProxy
2298-
2299-
if isinstance(self, FastMCPProxy):
2300-
try:
2301-
client_id = add_node("Client Factory", "Client")
2302-
add_connection(main_server_id, client_id, "proxies to")
2303-
except Exception:
2304-
# In case of any issues, skip
2305-
pass
2306-
2307-
# Add styling
2308-
lines.extend(
2309-
[
2310-
"",
2311-
" %% Styling",
2312-
" classDef serverClass fill:#e1f5fe,stroke:#01579b,stroke-width:2px",
2313-
" classDef proxyClass fill:#fff3e0,stroke:#e65100,stroke-width:2px",
2314-
" classDef clientClass fill:#f3e5f5,stroke:#4a148c,stroke-width:2px",
2315-
" classDef transportClass fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px",
2316-
]
2317-
)
2206+
@classmethod
2207+
def generate_name(cls, name: str | None = None) -> str:
2208+
class_name = cls.__name__
23182209

2319-
return "\n".join(lines)
2210+
if name is None:
2211+
return f"{class_name}-{secrets.token_hex(2)}"
2212+
else:
2213+
return f"{class_name}-{name}-{secrets.token_hex(2)}"
23202214

23212215

23222216
@dataclass

src/fastmcp/utilities/mcp_config.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
from typing import Any
22

3-
from fastmcp.client.transports import ClientTransport
3+
from fastmcp.client import Client
4+
from fastmcp.client.transports import (
5+
ClientTransport,
6+
SSETransport,
7+
StdioTransport,
8+
StreamableHttpTransport,
9+
)
410
from fastmcp.mcp_config import (
511
MCPConfig,
612
MCPServerTypes,
713
)
814
from fastmcp.server.server import FastMCP
15+
from fastmcp.server.proxy import ProxyClient
916

1017

1118
def mcp_config_to_servers_and_transports(
@@ -23,6 +30,8 @@ def mcp_server_type_to_servers_and_transports(
2330
mcp_server: MCPServerTypes,
2431
) -> tuple[str, FastMCP[Any], ClientTransport]:
2532
"""A utility function to convert each entry of an MCP Config into a transport and server."""
33+
import secrets
34+
2635
from fastmcp.mcp_config import (
2736
TransformingRemoteMCPServer,
2837
TransformingStdioMCPServer,
@@ -31,10 +40,21 @@ def mcp_server_type_to_servers_and_transports(
3140
server: FastMCP[Any]
3241
transport: ClientTransport
3342

43+
token = secrets.token_hex(2)
44+
45+
client_name = ProxyClient.generate_name(f"MCP_{name}_{token}")
46+
server_name = FastMCP.generate_name(f"MCP_{name}_{token}")
47+
3448
if isinstance(mcp_server, TransformingRemoteMCPServer | TransformingStdioMCPServer):
35-
server, transport = mcp_server._to_server_and_underlying_transport()
49+
server, transport = mcp_server._to_server_and_underlying_transport(
50+
server_name=server_name,
51+
client_name=client_name,
52+
)
3653
else:
3754
transport = mcp_server.to_transport()
38-
server = FastMCP.as_proxy(backend=transport)
55+
client: ProxyClient[StreamableHttpTransport | SSETransport | StdioTransport] = (
56+
ProxyClient(transport=transport, name=client_name)
57+
)
58+
server = FastMCP.as_proxy(name=server_name, backend=client)
3959

4060
return name, server, transport

0 commit comments

Comments
 (0)