Repository navigation
Description
Description
As per FastMCP's documentation:
Some MCP clients only support tools. They cannot list or read resources directly because they lack resource protocol support.
An example of this is Cursor.
A workaround for this is that FastMCP provides the ResourcesAsTools transform to bridge this gap by exposing list_resources and read_resource as tools.
It would be great if we support this feature here. Would love to open & scope a PR here if it's something we should support.
Additional Implementation Considerations
However, the above still requires tool-only clients (and the LLM using them) to:
- Call
list_resourcesto discover available resources/templates. - Identify the relevant URI or URI template.
- Construct the resource URI manually for templates.
- Call
read_resourcewith that URI.
This is particularly awkward for resource templates, where the parameters are already known by the resource definition but are not exposed as a tool input schema.
For example:
@mcp.resource("user://{user_id}/profile")
def user_profile(user_id: str) -> str:
...A tool-only client currently has to discover the template and construct:
user://42/profile
before calling read_resource.
It would be great if ResourcesAsTools could optionally expose each resource and resource template as an individual tool.
For example:
@mcp.resource("config://app")
def app_config() -> str:
...
@mcp.resource("user://{user_id}/profile")
def user_profile(user_id: str) -> str:
...
mcp.add_transform(ResourcesAsTools(mcp, individual_tools=True))could expose tools resembling:
app_config()
user_profile(user_id: str)
The generated tools would:
- Reuse the resource's name, description, MIME type, and other metadata where appropriate.
- Derive their input schema from the resource template parameters.
- Internally resolve/read the corresponding resource.
- Preserve the resource URI in the tool result where possible.
- Continue to respect the server's existing middleware, authorization, visibility, and other resource-level behavior.
This would make resources much more natural to consume from clients that only support tools/list / tools/call, while keeping the existing ResourcesAsTools behavior available for applications that prefer the generic list_resources / read_resource interface.
Example
Given:
@mcp.resource("data://{dataset}/{year}")
def dataset(dataset: str, year: int) -> str:
...a generated tool could look conceptually like:
{
"name": "dataset",
"description": "...",
"inputSchema": {
"type": "object",
"properties": {
"dataset": {"type": "string"},
"year": {"type": "integer"}
},
"required": ["dataset", "year"]
}
}The client could then simply call:
await client.call_tool(
"dataset",
{"dataset": "election_results", "year": 2022}
)without needing to know how the underlying resource URI is constructed.
Alternatives considered
If maintainer's want to support this, the existing list_resources + read_resource approach works, but it exposes the resource protocol's URI-oriented abstraction directly to the model. Generating tools would instead let tool-only clients consume the same resources through the interface they already understand.
Proposed Scope
We should PR iteratively:
- Add FastMCP-stype
list_resourcesandread_resourcetools. - Optional Add
individual_tool=True. This allows for one tool per resource/template with anInputSchema.
Other
- Willing to submit PR
- AI Assistance (Cursor)
References
No response
Activity
[-][FastMCP] Support for ResourcesAsTools[/-][+][MCPServer] Optional ResourcesAsTools support ala FastMCP[/+]ApparentlyTejas commentedon Nov 7, 2026
We have a resource template that's basically this exact shape : evidence://{evidence_id} in an MCP server I maintain, resolving a stored audit-ledger entry by id. Right now it's only usable from clients that speak the resource protocol (Claude Desktop/Code over stdio); a tool-only client like Cursor has no way to call it without list_resources/read_resource support, so this proposal would be the difference between that resource being reachable there or not.
One thing worth scoping explicitly: resources don't currently have an equivalent of ToolAnnotations (readOnlyHint, destructiveHint, etc.) the way tools do. If ResourcesAsTools auto-generates a tool per resource, should it default those hints (most resources are inherently read-only, so readOnlyHint=True seems like a safe default), or leave them unset until the resource API grows its own annotations? Happy to test against a real server with resource templates if that'd help once there's a draft.
ApparentlyTejas commentedon Nov 7, 2026
Been ~6 weeks quiet here, so: are you still planning to scope/open the PR? If so, no need to read further, happy to just watch and test against it once there's a draft, as I mentioned above. If not, I prototyped it to check feasibility and it's small enough that I'd be glad to pick up Phase 1.
The core idea works cleanly with what's already in
MCPServertoday, no new infrastructure needed, just wiring. For each resource/template: build a wrapper function with the same signature as the template's underlying function (inspect.signature(template.fn)), register it viamcp.add_tool(...), and let the existingfunc_metadatamachinery derive the input schema automatically, it's the exact same code path@mcp.tool()already uses, so there's no hand-rolled JSON Schema generation to get wrong. On call, resolve the URI from the kwargs and go through the realResourceTemplate.matches()/create_resource(), not a parallel reimplementation of resource logic.~25 lines, tested against
mcp==2.3.0:Against your example from the issue, this produces exactly the tool shape you described:
dataset(dataset: str, year: int)with a schema generated from the real function signature, and calling it round-trips through the actual resource template machinery and returns the real content. Also checked it against a value needing percent-encoding (search(query="hello world")→search://hello%20world) to make sureUriTemplate.expand()(not plainstr.format) handles that correctly — it does.One open question this surfaced that's worth scoping explicitly: context propagation. I pass
context=Noneintocreate_resource, which is safe today becauseinject_contextonly uses it when the resource function itself declares a context parameter, but a resource that does wantContext(for logging, elicitation, etc.) needs the generated tool to also declare a context param and forward it, the same way@mcp.tool()-decorated functions do viafind_context_parameter. Should the bridging layer detect that automatically (checktemplate.context_kwarg, add a matchingctx: Contextparam to the generated tool, forward it through), or is that out of scope for a first pass?Happy to turn this into a proper PR (with tests, and the context-propagation question above needs an answer first) if that's useful and you're not already deep into one.