Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions src/fastmcp/client/mixins/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,18 @@ async def list_prompts_mcp(
RuntimeError: If called while the client is not connected.
McpError: If the request results in a TimeoutError | JSONRPCError
"""
logger.debug(f"[{self.name}] called list_prompts")
with client_span(
"prompts/list",
"prompts/list",
"",
session_id=self.transport.get_session_id(),
):
logger.debug(f"[{self.name}] called list_prompts")

result = await self._await_with_session_monitoring(
self.session.list_prompts(cursor=cursor)
)
return result
result = await self._await_with_session_monitoring(
self.session.list_prompts(cursor=cursor)
)
return result

async def list_prompts(
self: Client,
Expand Down
32 changes: 22 additions & 10 deletions src/fastmcp/client/mixins/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,18 @@ async def list_resources_mcp(
RuntimeError: If called while the client is not connected.
McpError: If the request results in a TimeoutError | JSONRPCError
"""
logger.debug(f"[{self.name}] called list_resources")
with client_span(
"resources/list",
"resources/list",
"",
session_id=self.transport.get_session_id(),
):
logger.debug(f"[{self.name}] called list_resources")

result = await self._await_with_session_monitoring(
self.session.list_resources(cursor=cursor)
)
return result
result = await self._await_with_session_monitoring(
self.session.list_resources(cursor=cursor)
)
return result

async def list_resources(
self: Client,
Expand Down Expand Up @@ -118,12 +124,18 @@ async def list_resource_templates_mcp(
RuntimeError: If called while the client is not connected.
McpError: If the request results in a TimeoutError | JSONRPCError
"""
logger.debug(f"[{self.name}] called list_resource_templates")
with client_span(
"resources/templates/list",
"resources/templates/list",
"",
session_id=self.transport.get_session_id(),
):
logger.debug(f"[{self.name}] called list_resource_templates")

result = await self._await_with_session_monitoring(
self.session.list_resource_templates(cursor=cursor)
)
return result
result = await self._await_with_session_monitoring(
self.session.list_resource_templates(cursor=cursor)
)
return result

async def list_resource_templates(
self: Client,
Expand Down
16 changes: 11 additions & 5 deletions src/fastmcp/client/mixins/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,18 @@ async def list_tools_mcp(
RuntimeError: If called while the client is not connected.
McpError: If the request results in a TimeoutError | JSONRPCError
"""
logger.debug(f"[{self.name}] called list_tools")
with client_span(
"tools/list",
"tools/list",
"",
session_id=self.transport.get_session_id(),
):
logger.debug(f"[{self.name}] called list_tools")

result = await self._await_with_session_monitoring(
self.session.list_tools(cursor=cursor)
)
return result
result = await self._await_with_session_monitoring(
self.session.list_tools(cursor=cursor)
)
return result

async def list_tools(
self: Client,
Expand Down
176 changes: 96 additions & 80 deletions src/fastmcp/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -620,31 +620,33 @@ async def list_tools(self, *, run_middleware: bool = True) -> Sequence[Tool]:
call_next=lambda context: self.list_tools(run_middleware=False),
)

# Get all tools, apply session transforms, then filter enabled
# and model-visible (app-only tools are hidden from the model).
tools = list(await super().list_tools())
tools = await apply_session_transforms(tools)
tools = [t for t in tools if is_enabled(t) and _is_model_visible(t)]

# Rewrite per-tool Prefab renderer URIs based on the tool's
# mount-point address. The walk pairs each tool with the
# provider that yielded it, computes the hashed URI, and
# produces a model_copy with the URI in place. Original
# Tool objects are not mutated.
tools = self._rewrite_prefab_uris(tools)

skip_auth, token = _get_auth_context()
authorized: list[Tool] = []
for tool in tools:
if not skip_auth and tool.auth is not None:
ctx = AuthContext(token=token, component=tool)
try:
if not await run_auth_checks(tool.auth, ctx):
# Core logic: list tools
with server_span("tools/list", "tools/list", self.name, "tool", ""):
# Get all tools, apply session transforms, then filter enabled
# and model-visible (app-only tools are hidden from the model).
tools = list(await super().list_tools())
tools = await apply_session_transforms(tools)
tools = [t for t in tools if is_enabled(t) and _is_model_visible(t)]

# Rewrite per-tool Prefab renderer URIs based on the tool's
# mount-point address. The walk pairs each tool with the
# provider that yielded it, computes the hashed URI, and
# produces a model_copy with the URI in place. Original
# Tool objects are not mutated.
tools = self._rewrite_prefab_uris(tools)

skip_auth, token = _get_auth_context()
authorized: list[Tool] = []
for tool in tools:
if not skip_auth and tool.auth is not None:
ctx = AuthContext(token=token, component=tool)
try:
if not await run_auth_checks(tool.auth, ctx):
continue
except AuthorizationError:
continue
except AuthorizationError:
continue
authorized.append(tool)
return authorized
authorized.append(tool)
return authorized

async def _get_tool(
self, name: str, version: VersionSpec | None = None
Expand Down Expand Up @@ -754,32 +756,36 @@ async def list_resources(
call_next=lambda context: self.list_resources(run_middleware=False),
)

# Get all resources, apply session transforms, then filter enabled
resources = list(await super().list_resources())
resources = await apply_session_transforms(resources)
resources = [r for r in resources if is_enabled(r)]

# Append synthetic Prefab renderer resources — one per
# prefab tool, hashed by mount address. These don't live on
# any provider's storage; they're computed on demand.
from fastmcp.server.providers.prefab_synthesis import (
synthesize_prefab_resources,
)

resources.extend(await synthesize_prefab_resources(self))
# Core logic: list resources
with server_span(
"resources/list", "resources/list", self.name, "resource", ""
):
# Get all resources, apply session transforms, then filter enabled
resources = list(await super().list_resources())
resources = await apply_session_transforms(resources)
resources = [r for r in resources if is_enabled(r)]

# Append synthetic Prefab renderer resources — one per
# prefab tool, hashed by mount address. These don't live on
# any provider's storage; they're computed on demand.
from fastmcp.server.providers.prefab_synthesis import (
synthesize_prefab_resources,
)

skip_auth, token = _get_auth_context()
authorized: list[Resource] = []
for resource in resources:
if not skip_auth and resource.auth is not None:
ctx = AuthContext(token=token, component=resource)
try:
if not await run_auth_checks(resource.auth, ctx):
resources.extend(await synthesize_prefab_resources(self))

skip_auth, token = _get_auth_context()
authorized: list[Resource] = []
for resource in resources:
if not skip_auth and resource.auth is not None:
ctx = AuthContext(token=token, component=resource)
try:
if not await run_auth_checks(resource.auth, ctx):
continue
except AuthorizationError:
continue
except AuthorizationError:
continue
authorized.append(resource)
return authorized
authorized.append(resource)
return authorized

async def _get_resource(
self, uri: str, version: VersionSpec | None = None
Expand Down Expand Up @@ -887,23 +893,31 @@ async def list_resource_templates(
),
)

# Get all templates, apply session transforms, then filter enabled
templates = list(await super().list_resource_templates())
templates = await apply_session_transforms(templates)
templates = [t for t in templates if is_enabled(t)]

skip_auth, token = _get_auth_context()
authorized: list[ResourceTemplate] = []
for template in templates:
if not skip_auth and template.auth is not None:
ctx = AuthContext(token=token, component=template)
try:
if not await run_auth_checks(template.auth, ctx):
# Core logic: list resource templates
with server_span(
"resources/templates/list",
"resources/templates/list",
self.name,
"resource_template",
"",
):
# Get all templates, apply session transforms, then filter enabled
templates = list(await super().list_resource_templates())
templates = await apply_session_transforms(templates)
templates = [t for t in templates if is_enabled(t)]

skip_auth, token = _get_auth_context()
authorized: list[ResourceTemplate] = []
for template in templates:
if not skip_auth and template.auth is not None:
ctx = AuthContext(token=token, component=template)
try:
if not await run_auth_checks(template.auth, ctx):
continue
except AuthorizationError:
continue
except AuthorizationError:
continue
authorized.append(template)
return authorized
authorized.append(template)
return authorized

async def _get_resource_template(
self, uri: str, version: VersionSpec | None = None
Expand Down Expand Up @@ -1011,23 +1025,25 @@ async def list_prompts(self, *, run_middleware: bool = True) -> Sequence[Prompt]
call_next=lambda context: self.list_prompts(run_middleware=False),
)

# Get all prompts, apply session transforms, then filter enabled
prompts = list(await super().list_prompts())
prompts = await apply_session_transforms(prompts)
prompts = [p for p in prompts if is_enabled(p)]

skip_auth, token = _get_auth_context()
authorized: list[Prompt] = []
for prompt in prompts:
if not skip_auth and prompt.auth is not None:
ctx = AuthContext(token=token, component=prompt)
try:
if not await run_auth_checks(prompt.auth, ctx):
# Core logic: list prompts
with server_span("prompts/list", "prompts/list", self.name, "prompt", ""):
# Get all prompts, apply session transforms, then filter enabled
prompts = list(await super().list_prompts())
prompts = await apply_session_transforms(prompts)
prompts = [p for p in prompts if is_enabled(p)]

skip_auth, token = _get_auth_context()
authorized: list[Prompt] = []
for prompt in prompts:
if not skip_auth and prompt.auth is not None:
ctx = AuthContext(token=token, component=prompt)
try:
if not await run_auth_checks(prompt.auth, ctx):
continue
except AuthorizationError:
continue
except AuthorizationError:
continue
authorized.append(prompt)
return authorized
authorized.append(prompt)
return authorized

async def _get_prompt(
self, name: str, version: VersionSpec | None = None
Expand Down
Loading
Loading