FastestMCP has two transform layers:
- server transforms, which rewrite or filter components inside a server
- provider transforms, which rewrite provider-backed identifiers as they are exposed to the parent server
Both exist because composition creates two different needs:
- sometimes you want to change how a server presents its own components
- sometimes you want to mount a provider but rename or namespace what it exposes without changing the source
Server Transforms
Server transforms are added with FastestMCP.add_transform/2.
They receive the component plus the current operation and can:
- return the component unchanged
- return a modified component
- return
nilto filter it out - mark it disabled so later policy rejects it
transform = fn component, _operation ->
if FastestMCP.Component.identifier(component) == "internal" do
%{component | enabled: false}
else
component
end
end
server =
FastestMCP.server("transforms")
|> FastestMCP.add_transform(transform)
|> FastestMCP.add_tool("public", fn _args, _ctx -> :ok end)
|> FastestMCP.add_tool("internal", fn _args, _ctx -> :ok end)This is useful when the filtering rule belongs to the server itself rather than to a mounted provider.
Provider Transforms
Provider transforms rewrite provider-backed identifiers while preserving reverse lookup back to the original component.
FastestMCP exposes them through FastestMCP.add_provider_transform/2:
provider =
FastestMCP.Providers.Local.new(name: "dynamic")
|> FastestMCP.Providers.Local.add_tool("echo", fn arguments, _ctx -> arguments end)
|> FastestMCP.add_provider_transform(
FastestMCP.ProviderTransforms.Namespace.new("tools")
)That lets the mounted provider expose tools_echo while still resolving the
backing component correctly.
Namespacing Mounted Components
The most common provider transform is namespacing.
child =
FastestMCP.server("child")
|> FastestMCP.add_tool("echo", fn arguments, _ctx -> arguments end)
parent =
FastestMCP.server("parent")
|> FastestMCP.mount(child, namespace: "child")Mounted tools become:
child_echochild_...for prompts- namespaced URIs for resources and resource templates
Use namespacing whenever the parent and child may expose overlapping names.
Tool Renaming
Provider tool transforms can rename provider-backed tools without changing the source provider:
provider =
FastestMCP.Providers.Local.new(name: "dynamic")
|> FastestMCP.Providers.Local.add_tool("dynamic_echo", fn arguments, _ctx -> arguments end)
|> FastestMCP.add_provider_transform(
FastestMCP.ProviderTransforms.ToolTransform.new(%{
"dynamic_echo" => %{name: "echo"}
})
)Clients now see echo, but calls are still mapped back to dynamic_echo
behind the scenes.
Stacking Transforms
Provider transforms can be stacked:
provider =
child
|> FastestMCP.Providers.MountedServer.new()
|> FastestMCP.add_provider_transform(
FastestMCP.ProviderTransforms.Namespace.new("child")
)
|> FastestMCP.add_provider_transform(
FastestMCP.ProviderTransforms.ToolTransform.new(%{
"child_echo" => %{name: "short"}
})
)That pattern is useful when you want collision safety first and a curated public name second.
Tool-only Clients
FastestMCP v0.1 does not ship a public tool-search transform, but it does ship tool-injection middleware helpers for tool-only clients:
Those helpers expose synthetic tools that bridge prompt and resource workflows through the normal tool surface.
Transforms vs Middleware
Use transforms when you are shaping component identity.
Use Middleware when you are shaping request execution.
In practice:
- namespacing, renaming, filtering: transforms
- logging, retry, caching, rate limiting: middleware
Why This Shape
FastestMCP separates "what components exist" from "how requests run."
That split keeps composition understandable. Providers and transforms define the catalog. Middleware defines execution behavior. Mixing those two concerns tends to make mounted systems much harder to debug.