tools/list, resources/list, or prompts/list, but it remains fully callable by its original name or URI. You can present a clean public surface and still call the hidden internals yourself. You register transforms fluently with server.transform(t), or all at once through FastMCPOptions.transforms, and they apply in registration order.
View projections
A transform operates on read-only views of your components — lightweight snapshots of the fields that show up in a list response. Each primitive has its own view type, and a transform method receives the current view and returns the view it wants clients to see, ornull to hide the component. Because the input is a snapshot rather than the live registration, a transform cannot accidentally mutate your server; it can only describe a projection.
Returning
null from a view method hides that component from the list. It stays callable by its original identifier — the projection governs visibility, not reachability.
Built-in transforms
The built-ins cover the common projections, so most servers compose them rather than write their own. Reach forrenameTool or redescribeTool when a tool’s registered name or description is right for your code but wrong for the model — a single, surgical rewrite. Reach for FilterTransform to carve a subset out of a larger registry by predicate, the workhorse for tailoring a public surface. Reach for NamespaceTransform when you want every advertised name to carry a prefix, and VersionFilter when you tag components by version and want to expose one version at a time.
The URI invariant
NamespaceTransform prefixes every advertised name, but it never touches resource URIs, and that asymmetry is deliberate. A resource URI is an identifier under RFC 3986, and its scheme — the part before the :// — is structurally significant. Rewriting data://reports/q1 to v1_data://reports/q1 produces a string that is no longer a valid URI: v1_data is not a registered scheme, and clients that parse the URI to dereference it will fail. A display name is a human-facing label and is free to change; a URI is the address the client reads by, and changing it would break dereferencing. So namespacing prefixes the name a client sees in a list and leaves the URI alone — the client reads the resource by its original, valid URI, and routing matches it there.
Resources and prompts as tools
Some clients consume tools fluently but have no first-class handling for resources or prompts.ResourcesAsTools() and PromptsAsTools() bridge that gap by synthesizing a tool — a list_resources or list_prompts tool that returns the currently visible resource or prompt views as its result. The model can then discover and reach that data through the one primitive it understands.
Synthesis happens after filtering and runs through the transform chain itself, which keeps the synthesized tools consistent with everything else. The views handed to synthesis are already filtered for visibility and auth, so a synthesized listing never exposes a component the caller couldn’t otherwise see. And because the synthesized tool is then transformed like any other, a NamespaceTransform('v1') registered alongside renames it to v1_list_resources, and a FilterTransform can hide it — the synthesized tool obeys the same projection rules as a registered one.
Routing
When transforms are active, what a client sees in a list and how a call resolves are two separate questions, and routing answers the second so that hidden-but-callable actually works. A call resolves against the underlying registry, not the projected view, which is what lets you invoke a hidden component by its original identifier. For a tool call, FastMCP checks synthesized tools by name first, then scans registered tools through thetransformTool chain looking for a name match — so a renamed tool resolves under its new name — and finally falls back to a direct registry lookup by original name, which is how a hidden tool stays reachable. Prompt resolution scans the transformed prompts first, then falls back to the registry the same way. Resource reads try a direct URI match, then a direct template match, then scan statics and templates through their transform chains for a URI match. In every case the projection decides visibility and the registry decides reachability, so renaming, namespacing, and filtering never strand a component you still need to call.