Add type-prefixed keys for globally unique component identification#2704
Add type-prefixed keys for globally unique component identification#2704
Conversation
Components now have globally unique keys with type prefixes:
- Tool "foo" → "tool:foo"
- Prompt "bar" → "prompt:bar"
- Resource "file://x" → "resource:file://x"
- ResourceTemplate "data://{id}" → "template:data://{id}"
Key changes:
- Add KEY_PREFIX class variable and make_key() classmethod to components
- Unify LocalProvider storage into single _components dict
- Add get_component(key) method to Provider and FastMCP for direct key lookup
- Simplify get_tasks() to return Sequence[FastMCPComponent]
- Add __init_subclass__ warning for missing KEY_PREFIX
WalkthroughThis pull request implements universal component key prefixing to make keys unique across different component types. It introduces type-specific prefixes (tool, prompt, resource, template) prepended to component identifiers and shifts public lookup APIs from generic "key" parameters to semantic identifiers (name for tools/prompts, uri for resources/templates). Changes include: adding Possibly related PRs
Pre-merge checks and finishing touches✅ Passed checks (5 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
docs/development/upgrade-guide.mdx (1)
11-33: Clear migration documentation, but consider mentioning get_component.The upgrade guide clearly documents the parameter name changes with helpful examples. However, consider adding a note about the new
get_component(key)method that accepts prefixed keys (e.g.,"tool:my_tool"), as this is part of the broader refactoring mentioned in the PR objectives.📝 Suggested addition
After line 32, consider adding:
### New get_component() Method A new `get_component(key)` method has been added that accepts fully-prefixed keys: ```python # New unified lookup with prefixed keys tool = await mcp.get_component("tool:my_tool") resource = await mcp.get_component("resource:file://path")This complements the semantic lookup methods and enables generic component handling.
</details> </blockquote></details> <details> <summary>src/fastmcp/server/providers/transforming.py (1)</summary><blockquote> `263-297`: **Consider handling unknown component types in transformation loop.** The loop transforms known component types (Tool, ResourceTemplate, Resource, Prompt) but silently drops any other `FastMCPComponent` subclasses. While this may be intentional since only these four types exist currently, it could lead to subtle bugs if new component types are added in the future. <details> <summary>🔎 Optional: Add fallback or warning for unknown types</summary> ```diff elif isinstance(component, Prompt): transformed.append( component.model_copy( update={"name": self._transform_prompt_name(component.name)} ) ) + else: + # Pass through unknown component types unchanged + transformed.append(component) return transformedsrc/fastmcp/contrib/component_manager/component_service.py (1)
177-202: Minor: Consider renamingkeyparameter tonamefor consistency.The prompt methods use
keyas the parameter name while tool methods usename. For consistency with the semantic identifier pattern (tools and prompts use names), consider renaming:🔎 Optional: Rename for consistency
- async def _enable_prompt(self, key: str) -> Prompt: + async def _enable_prompt(self, name: str) -> Prompt: """Handle 'enablePrompt' requests. Args: - key: The key of the prompt to enable + name: The name of the prompt to enable Returns: The prompt that was enabled """ - logger.debug("Enabling prompt: %s", key) + logger.debug("Enabling prompt: %s", name) # 1. Check local prompts first. The server will have already applied its filter. - if Prompt.make_key(key) in self._server._local_provider._components: - prompt: Prompt = await self._server.get_prompt(key) + if Prompt.make_key(name) in self._server._local_provider._components: + prompt: Prompt = await self._server.get_prompt(name) prompt.enable() return prompt # 2. Check mounted servers via FastMCPProvider/TransformingProvider for provider in self._server._providers: - result = _get_mounted_server_and_key(provider, key, "prompt") + result = _get_mounted_server_and_key(provider, name, "prompt") if result is not None: server, unprefixed = result mounted_service = ComponentService(server) prompt = await mounted_service._enable_prompt(unprefixed) return prompt - raise NotFoundError(f"Unknown prompt: {key}") + raise NotFoundError(f"Unknown prompt: {name}")src/fastmcp/server/server.py (1)
739-739: Consider extracting error messages to constants.Static analysis flags these f-string error messages (TRY003). While this is a common pattern in the codebase, extracting messages to constants or factory methods could improve consistency and make them easier to test.
Example refactor pattern
# At module level or in exceptions module def tool_not_found_error(name: str) -> NotFoundError: return NotFoundError(f"Unknown tool: {name}") # Usage raise tool_not_found_error(name)Based on learnings, review and update related Manager classes when modifying MCP object definitions, though this file appears to be the server façade rather than a manager class.
Also applies to: 810-810, 849-849, 888-888, 915-915
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (4)
tests/server/providers/test_local_provider.pyis excluded by none and included by nonetests/server/tasks/test_custom_subclass_tasks.pyis excluded by none and included by nonetests/server/tasks/test_server_tasks_parameter.pyis excluded by none and included by nonetests/utilities/test_components.pyis excluded by none and included by none
📒 Files selected for processing (17)
docs/development/upgrade-guide.mdxsrc/fastmcp/contrib/component_manager/component_service.pysrc/fastmcp/prompts/prompt.pysrc/fastmcp/resources/resource.pysrc/fastmcp/resources/template.pysrc/fastmcp/server/providers/base.pysrc/fastmcp/server/providers/fastmcp_provider.pysrc/fastmcp/server/providers/local_provider.pysrc/fastmcp/server/providers/openapi/provider.pysrc/fastmcp/server/providers/proxy.pysrc/fastmcp/server/providers/transforming.pysrc/fastmcp/server/server.pysrc/fastmcp/server/tasks/requests.pysrc/fastmcp/tools/tool.pysrc/fastmcp/tools/tool_transform.pysrc/fastmcp/utilities/components.pysrc/fastmcp/utilities/inspect.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
**/*.py: Use Python ≥ 3.10 with full type annotations
Never use bareexcept- be specific with exception types
Files:
src/fastmcp/tools/tool.pysrc/fastmcp/utilities/inspect.pysrc/fastmcp/tools/tool_transform.pysrc/fastmcp/server/providers/fastmcp_provider.pysrc/fastmcp/server/providers/openapi/provider.pysrc/fastmcp/resources/template.pysrc/fastmcp/server/tasks/requests.pysrc/fastmcp/resources/resource.pysrc/fastmcp/server/providers/proxy.pysrc/fastmcp/prompts/prompt.pysrc/fastmcp/utilities/components.pysrc/fastmcp/server/providers/transforming.pysrc/fastmcp/server/providers/base.pysrc/fastmcp/contrib/component_manager/component_service.pysrc/fastmcp/server/providers/local_provider.pysrc/fastmcp/server/server.py
docs/**/*.mdx
📄 CodeRabbit inference engine (docs/.cursor/rules/mintlify.mdc)
docs/**/*.mdx: Use clear, direct language appropriate for technical audiences
Write in second person ('you') for instructions and procedures in MDX documentation
Use active voice over passive voice in MDX technical documentation
Employ present tense for current states and future tense for outcomes in MDX documentation
Maintain consistent terminology throughout all MDX documentation
Keep sentences concise while providing necessary context in MDX documentation
Use parallel structure in lists, headings, and procedures in MDX documentation
Lead with the most important information using inverted pyramid structure in MDX documentation
Use progressive disclosure in MDX documentation: present basic concepts before advanced ones
Break complex procedures into numbered steps in MDX documentation
Include prerequisites and context before instructions in MDX documentation
Provide expected outcomes for each major step in MDX documentation
End sections with next steps or related information in MDX documentation
Use descriptive, keyword-rich headings for navigation and SEO in MDX documentation
Focus on user goals and outcomes rather than system features in MDX documentation
Anticipate common questions and address them proactively in MDX documentation
Include troubleshooting for likely failure points in MDX documentation
Provide multiple pathways (beginner vs advanced) but offer an opinionated path to avoid overwhelming users in MDX documentation
Always include complete, runnable code examples that users can copy and execute in MDX documentation
Show proper error handling and edge case management in MDX code examples
Use realistic data instead of placeholder values in MDX code examples
Include expected outputs and results for verification in MDX code examples
Test all code examples thoroughly before publishing in MDX documentation
Specify language and include filename when relevant in MDX code examples
Add explanatory comments for complex logic in MDX code examples
Document all API...
Files:
docs/development/upgrade-guide.mdx
🧠 Learnings (3)
📚 Learning: 2025-12-21T21:37:55.031Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T21:37:55.031Z
Learning: Applies to src/fastmcp/**/__init__.py : Core types that define a module's purpose should be exported (e.g., `Middleware` from `fastmcp.server.middleware`), while specialized features can live in submodules
Applied to files:
src/fastmcp/server/providers/transforming.py
📚 Learning: 2025-12-21T21:37:55.031Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T21:37:55.031Z
Learning: Applies to src/fastmcp/__init__.py : All module exports should be intentional - only re-export to `fastmcp.*` for fundamental types like `FastMCP` and `Client`, prefer users importing from specific submodules for specialized features
Applied to files:
src/fastmcp/server/providers/transforming.py
📚 Learning: 2025-11-26T21:51:44.174Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: .cursor/rules/core-mcp-objects.mdc:0-0
Timestamp: 2025-11-26T21:51:44.174Z
Learning: Review and update related Manager classes (ToolManager, ResourceManager, PromptManager) when modifying MCP object definitions
Applied to files:
src/fastmcp/contrib/component_manager/component_service.pysrc/fastmcp/server/server.py
🧬 Code graph analysis (11)
src/fastmcp/utilities/inspect.py (2)
src/fastmcp/contrib/mcp_mixin/mcp_mixin.py (1)
mcp_tool(25-52)src/fastmcp/tools/tool.py (2)
to_mcp_tool(173-199)to_mcp_tool(378-398)
src/fastmcp/tools/tool_transform.py (4)
src/fastmcp/tools/tool.py (1)
Tool(127-372)src/fastmcp/resources/resource.py (1)
key(304-306)src/fastmcp/resources/template.py (1)
key(269-271)src/fastmcp/utilities/components.py (1)
key(99-107)
src/fastmcp/server/providers/fastmcp_provider.py (3)
src/fastmcp/server/providers/base.py (2)
Provider(42-323)get_tasks(258-293)src/fastmcp/utilities/components.py (2)
FastMCPComponent(34-183)key(99-107)src/fastmcp/resources/resource.py (1)
key(304-306)
src/fastmcp/server/providers/openapi/provider.py (5)
src/fastmcp/server/providers/base.py (2)
Provider(42-323)get_tasks(258-293)src/fastmcp/utilities/components.py (1)
FastMCPComponent(34-183)src/fastmcp/server/providers/fastmcp_provider.py (1)
get_tasks(560-573)src/fastmcp/server/providers/local_provider.py (1)
get_tasks(312-319)src/fastmcp/server/providers/proxy.py (1)
get_tasks(544-551)
src/fastmcp/resources/template.py (1)
src/fastmcp/utilities/components.py (1)
make_key(85-96)
src/fastmcp/server/tasks/requests.py (6)
src/fastmcp/server/tasks/keys.py (1)
parse_task_key(47-77)src/fastmcp/resources/resource.py (2)
Resource(137-333)convert_result(236-241)src/fastmcp/resources/template.py (2)
ResourceTemplate(97-300)convert_result(182-187)src/fastmcp/tools/tool.py (2)
Tool(127-372)convert_result(246-284)src/fastmcp/server/providers/base.py (1)
get_component(228-252)src/fastmcp/server/dependencies.py (1)
message(426-427)
src/fastmcp/resources/resource.py (1)
src/fastmcp/utilities/components.py (1)
make_key(85-96)
src/fastmcp/utilities/components.py (2)
src/fastmcp/resources/resource.py (1)
key(304-306)src/fastmcp/resources/template.py (1)
key(269-271)
src/fastmcp/server/providers/transforming.py (8)
src/fastmcp/server/providers/base.py (2)
Provider(42-323)get_tasks(258-293)src/fastmcp/tools/tool.py (1)
Tool(127-372)src/fastmcp/utilities/components.py (1)
FastMCPComponent(34-183)src/fastmcp/server/providers/openapi/provider.py (1)
get_tasks(382-384)src/fastmcp/server/providers/proxy.py (1)
get_tasks(544-551)src/fastmcp/resources/template.py (1)
ResourceTemplate(97-300)src/fastmcp/resources/resource.py (1)
Resource(137-333)src/fastmcp/prompts/prompt.py (1)
Prompt(118-324)
src/fastmcp/server/providers/base.py (10)
src/fastmcp/utilities/components.py (2)
FastMCPComponent(34-183)key(99-107)src/fastmcp/server/providers/local_provider.py (13)
get_component(299-306)tool(326-342)tool(345-361)tool(363-499)list_tools(253-260)resource(501-624)list_resources(267-269)list_resource_templates(276-278)prompt(627-639)prompt(642-654)prompt(656-773)list_prompts(290-292)get_tasks(312-319)src/fastmcp/server/server.py (8)
get_component(890-915)tool(1745-1760)tool(1763-1778)tool(1780-1866)resource(1890-1975)prompt(1989-2001)prompt(2004-2016)prompt(2018-2115)src/fastmcp/resources/resource.py (2)
key(304-306)Resource(137-333)src/fastmcp/resources/template.py (2)
key(269-271)ResourceTemplate(97-300)src/fastmcp/prompts/prompt.py (2)
Prompt(118-324)FunctionPrompt(327-551)src/fastmcp/server/providers/fastmcp_provider.py (5)
list_tools(481-489)list_resources(500-508)list_resource_templates(519-528)list_prompts(542-549)get_tasks(560-573)src/fastmcp/server/providers/openapi/provider.py (5)
list_tools(351-353)list_resources(359-361)list_resource_templates(367-369)list_prompts(378-380)get_tasks(382-384)src/fastmcp/server/providers/transforming.py (5)
list_tools(169-175)list_resources(191-197)list_resource_templates(213-221)list_prompts(241-247)get_tasks(263-297)src/fastmcp/server/tasks/config.py (1)
supports_tasks(69-75)
src/fastmcp/server/server.py (6)
src/fastmcp/server/providers/base.py (1)
get_tasks(258-293)src/fastmcp/prompts/prompt.py (3)
register_with_docket(297-301)register_with_docket(518-526)Prompt(118-324)src/fastmcp/resources/resource.py (4)
register_with_docket(308-312)register_with_docket(420-428)Resource(137-333)key(304-306)src/fastmcp/resources/template.py (4)
register_with_docket(273-277)register_with_docket(383-391)ResourceTemplate(97-300)key(269-271)src/fastmcp/tools/tool.py (3)
register_with_docket(311-315)register_with_docket(483-491)Tool(127-372)src/fastmcp/utilities/components.py (3)
register_with_docket(155-161)key(99-107)make_key(85-96)
🪛 Ruff (0.14.10)
src/fastmcp/contrib/component_manager/component_service.py
86-86: Avoid specifying long messages outside the exception class
(TRY003)
113-113: Avoid specifying long messages outside the exception class
(TRY003)
144-144: Avoid specifying long messages outside the exception class
(TRY003)
175-175: Avoid specifying long messages outside the exception class
(TRY003)
src/fastmcp/server/providers/local_provider.py
129-129: Avoid specifying long messages outside the exception class
(TRY003)
159-159: Avoid specifying long messages outside the exception class
(TRY003)
src/fastmcp/server/server.py
739-739: Avoid specifying long messages outside the exception class
(TRY003)
810-810: Avoid specifying long messages outside the exception class
(TRY003)
849-849: Avoid specifying long messages outside the exception class
(TRY003)
888-888: Avoid specifying long messages outside the exception class
(TRY003)
915-915: Avoid specifying long messages outside the exception class
(TRY003)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Run tests: Python 3.10 on ubuntu-latest
- GitHub Check: Run tests with lowest-direct dependencies
- GitHub Check: Run tests: Python 3.13 on ubuntu-latest
- GitHub Check: Run tests: Python 3.10 on windows-latest
🔇 Additional comments (35)
src/fastmcp/utilities/inspect.py (3)
122-122: LGTM! Correctly uses semantic identifier.The change to use
tool.nameinstead oftool.keyis correct. With the new prefixed-key system,tool.keyreturns a prefixed value like"tool:greet", but the MCP protocol expects the unprefixed name.
168-168: LGTM! Correctly uses URI instead of prefixed key.Using
str(resource.uri)instead ofresource.keyis correct. The URI is the semantic identifier for resources, while the key is now prefixed (e.g.,"resource:file://x").
191-191: LGTM! Correctly uses uri_template instead of prefixed key.Consistent with the other changes, using
template.uri_templatedirectly is correct sincetemplate.keynow returns a prefixed value.src/fastmcp/prompts/prompt.py (1)
9-9: LGTM! KEY_PREFIX added correctly.The addition of
KEY_PREFIX: ClassVar[str] = "prompt"aligns with the pattern used across other component types (Tool, Resource, ResourceTemplate) and enables the unified keying system introduced in this PR.Also applies to: 121-122
src/fastmcp/tools/tool.py (1)
11-11: LGTM! KEY_PREFIX added correctly.The addition of
KEY_PREFIX: ClassVar[str] = "tool"follows the established pattern for component type prefixing.Also applies to: 130-131
src/fastmcp/resources/resource.py (2)
9-9: LGTM! KEY_PREFIX added correctly.The addition of
KEY_PREFIX: ClassVar[str] = "resource"follows the established pattern.Also applies to: 140-141
305-306: LGTM! Key property correctly uses make_key.The key property now returns a prefixed key via
self.make_key(str(self.uri)), which generates keys like"resource:file://x". The updated docstring accurately describes it as a "globally unique lookup key."src/fastmcp/resources/template.py (2)
8-8: LGTM! KEY_PREFIX added correctly.The addition of
KEY_PREFIX: ClassVar[str] = "template"follows the established pattern.Also applies to: 100-101
270-271: LGTM! Key property correctly uses make_key.The key property now returns a prefixed key via
self.make_key(self.uri_template), generating keys like"template:weather://{city}". The updated docstring accurately describes it as a "globally unique lookup key."src/fastmcp/utilities/components.py (4)
4-4: LGTM! ClassVar and KEY_PREFIX added correctly.The addition of
ClassVarimport and theKEY_PREFIX: ClassVar[str] = ""default value on the base class enables the type-prefixed key system.Also applies to: 37-37
39-52: LGTM! init_subclass provides helpful guidance.The
__init_subclass__hook issues a clear warning when subclasses don't defineKEY_PREFIX, helping developers adopt the new keying system. The exclusion ofMirroredComponentis appropriate given it's mentioned as a mixin.
84-97: LGTM! make_key implementation is correct.The
make_keyclassmethod correctly constructs prefixed keys (e.g.,"tool:name","resource:uri") whenKEY_PREFIXis defined, and returns the identifier unchanged otherwise for backward compatibility.
99-107: LGTM! Key property correctly uses make_key.The updated
keyproperty now returns a prefixed key viaself.make_key(self.name). The docstring clearly explains the format and notes that subclasses should override to use their specific identifier (as Resource and ResourceTemplate do).src/fastmcp/tools/tool_transform.py (1)
930-945: LGTM! Transformation logic correctly handles prefixed keys.The updated implementation correctly separates internal prefixed keys (used in the tools dict) from user-facing tool names (used in transformations). The logic:
- Looks up transformations by
tool.name(unprefixed)- Stores transformed tools using
transformed.key(prefixed)- Preserves original prefixed keys for non-transformed tools
The updated docstring clearly explains this distinction.
src/fastmcp/server/tasks/requests.py (1)
298-349: LGTM on the component type branching logic.The
isinstancechecks correctly handle all four component types with appropriate ordering (ResourceTemplate before Resource to avoid incorrect matching), and the else clause provides a clear error for unexpected types.src/fastmcp/server/providers/openapi/provider.py (1)
382-384: LGTM!The
get_tasks()method correctly returns an empty sequence since OpenAPI components don't support background tasks. The return type update toSequence[FastMCPComponent]aligns with the unified component model.src/fastmcp/server/providers/proxy.py (1)
544-551: LGTM!The
get_tasks()implementation correctly returns an empty list since all proxy components havetask_config.mode="forbidden". The docstring appropriately explains the design decision to avoid premature client initialization during server lifespan startup.src/fastmcp/server/providers/fastmcp_provider.py (2)
560-573: LGTM!The
get_tasks()method correctly aggregates task-eligible components from all wrapped server providers into a flatSequence[FastMCPComponent]. The iteration pattern properly handles nested provider hierarchies.
354-357: The docket key format change is correct and properly aligned with Docket registration.The code correctly sets
_docket_fn_keytoself.key(e.g.,"template:item://{id}"), which matches how components are registered with Docket and how they're looked up. Docket uses the prefixed key format for bothdocket.register(self.read, names=[self.key])anddocket.add(lookup_key)operations, so the change maintains consistency across all three operations: registration, contextvar setting, and lookup.However, the docstring at line 335 is outdated—it says "Sets _docket_fn_key to self.uri_template" but the code actually sets it to
self.key. Update the docstring to reflect the actual implementation.Likely an incorrect or invalid review comment.
src/fastmcp/server/providers/base.py (2)
228-252: LGTM on get_component default implementation.The default implementation iterates through all component types to find a match by prefixed key. While O(n) per component type, this is appropriate as a fallback since:
- Providers like
LocalProvideroverride with O(1) lookup- The docstring correctly indicates providers should override for efficiency
- It handles all component types consistently
258-293: LGTM on get_tasks implementation.The method correctly filters for function-based components (
FunctionTool,FunctionResource,FunctionResourceTemplate,FunctionPrompt) that havetask_config.supports_tasks() == True. The return type change toSequence[FastMCPComponent]aligns with the unified component model.src/fastmcp/contrib/component_manager/component_service.py (2)
61-86: LGTM on tool enable/disable with prefixed key lookup.The implementation correctly uses
Tool.make_key(name)to construct the prefixed key for component lookup in the unified_componentsstore, then delegates to mounted servers if not found locally.
115-144: LGTM on resource enable with fallback to template.The logic correctly tries
Resource.make_key(uri)first, then falls back toResourceTemplate.make_key(uri), handling both resource types with a single URI parameter.src/fastmcp/server/providers/local_provider.py (4)
109-110: LGTM on unified component storage.The single
_componentsdictionary keyed by prefixed keys (e.g.,"tool:greet","resource:file://x") is a clean design that prevents collisions between component types while enabling O(1) lookup.
117-146: LGTM on _add_component implementation.The generic method correctly:
- Handles all duplicate behaviors (error, warn, replace, ignore)
- Uses
component.keyfor storage (prefixed key)- Sends appropriate notifications based on component type
- Returns the correct type via TypeVar
_C
312-319: LGTM on get_tasks implementation.The implementation is concise and correct—iterating through the unified
_componentsand filtering bytask_config.supports_tasks(). This correctly includes all component types eligible for background tasks.
253-260: Implementation already handles key format correctly—no action needed.The
apply_transformations_to_toolsfunction is designed to accept tools keyed by prefixed key (e.g.,"tool:greet") and transformations keyed by tool name. It looks up transformations usingtool.name(not the prefixed key), so the new key format is compatible and transformations match as expected.src/fastmcp/server/server.py (8)
464-465: LGTM! Task registration now uses unified component model.The refactored code cleanly iterates over a flat sequence of components and delegates registration to each component's
register_with_docketmethod. This is more maintainable than the previous per-type iteration approach.
702-739: Public API correctly shifts to name-based tool retrieval.The methods now use
tool.namefor indexing and accept anameparameter instead ofkey. This aligns with the PR's goal of using semantic identifiers in public APIs while keeping prefixed keys internal.
772-810: LGTM! Resource retrieval now uses URI-based indexing.The shift from key-based to URI-based retrieval is consistent with the semantic identifier approach. Using
str(resource.uri)as the dictionary key is appropriate for URI-based indexing.
812-849: Template retrieval correctly implements URI matching.The method accepts an actual URI and finds a matching template by iterating through providers. The dictionary is indexed by
template.uri_templatefor deduplication, whileget_resource_template(uri)performs pattern matching.
851-888: LGTM! Prompt retrieval now uses name-based indexing.Consistent with tool retrieval, prompts are now indexed by name in the public API. The implementation correctly uses
prompt.nameas the dictionary key.
890-915: Newget_component()method enables prefixed-key lookups.This method provides a unified entry point for retrieving any component type using its prefixed key (e.g., "tool:name", "resource:uri"). It follows the same provider iteration pattern as the semantic-based retrieval methods.
988-992: MCP list handlers correctly pass semantic identifiers.The handlers now explicitly pass semantic identifiers (
namefor tools/prompts,uri/uriTemplatefor resources/templates) to the MCP representation methods. This ensures the MCP protocol uses the appropriate identifiers.Also applies to: 1053-1057, 1118-1122, 1184-1188
1281-1281: Handlers correctly construct prefixed keys for docket.The handlers use
Tool.make_key(),Resource.make_key(), andPrompt.make_key()to convert semantic identifiers (name/URI) to prefixed keys before setting them in context variables. This ensures docket task tracking uses the globally unique prefixed keys.Also applies to: 1325-1325, 1377-1377
| # Parse task key to get component key | ||
| key_parts = parse_task_key(task_key) | ||
| task_type = key_parts["task_type"] | ||
| component_id = key_parts["component_identifier"] | ||
| component_key = key_parts["component_identifier"] | ||
|
|
||
| # Look up component by its prefixed key | ||
| from fastmcp.prompts.prompt import Prompt | ||
| from fastmcp.resources.resource import Resource | ||
| from fastmcp.resources.template import ResourceTemplate | ||
| from fastmcp.tools.tool import Tool | ||
|
|
||
| component = await server.get_component(component_key) |
There was a problem hiding this comment.
Critical: Component lookup uses raw identifier instead of prefixed key.
The code extracts component_identifier from the parsed task key (e.g., "my_tool" or "file://data.txt"), but server.get_component() expects a prefixed key (e.g., "tool:my_tool" or "resource:file://data.txt").
You need to reconstruct the prefixed key using the task_type from the parsed key:
🔎 Proposed fix
# Parse task key to get component key
key_parts = parse_task_key(task_key)
- component_key = key_parts["component_identifier"]
+ task_type = key_parts["task_type"]
+ component_identifier = key_parts["component_identifier"]
+ component_key = f"{task_type}:{component_identifier}"
# Look up component by its prefixed key
from fastmcp.prompts.prompt import Prompt📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Parse task key to get component key | |
| key_parts = parse_task_key(task_key) | |
| task_type = key_parts["task_type"] | |
| component_id = key_parts["component_identifier"] | |
| component_key = key_parts["component_identifier"] | |
| # Look up component by its prefixed key | |
| from fastmcp.prompts.prompt import Prompt | |
| from fastmcp.resources.resource import Resource | |
| from fastmcp.resources.template import ResourceTemplate | |
| from fastmcp.tools.tool import Tool | |
| component = await server.get_component(component_key) | |
| # Parse task key to get component key | |
| key_parts = parse_task_key(task_key) | |
| task_type = key_parts["task_type"] | |
| component_identifier = key_parts["component_identifier"] | |
| component_key = f"{task_type}:{component_identifier}" | |
| # Look up component by its prefixed key | |
| from fastmcp.prompts.prompt import Prompt | |
| from fastmcp.resources.resource import Resource | |
| from fastmcp.resources.template import ResourceTemplate | |
| from fastmcp.tools.tool import Tool | |
| component = await server.get_component(component_key) |
There was a problem hiding this comment.
no - the component identifier is the compoennt key. no issue here.
There was a problem hiding this comment.
@jlowin Thank you for the clarification! You're absolutely right—I misunderstood what parse_task_key() returns. The component_identifier is already the full prefixed key (e.g., "tool:greet"), not just the bare identifier. My suggested "fix" would have actually broken the code by double-prefixing. Apologies for the confusion!
Test Failure AnalysisSummary: The test is failing on Python 3.13 (ubuntu-latest) because the rate limiter is not triggering as expected - the test expects a ToolError to be raised but the request completes successfully. Root Cause: This appears to be a race condition or timing issue specific to Python 3.13. The test sets up a rate limiter with:
The test then makes:
The test expects the 4th explicit operation (3rd
Suggested Solution: Make the test more deterministic by:
The most robust solution is option 1 (reduce burst_capacity to 4) as it's the minimal change and makes the test expectations match reality. Detailed AnalysisFailure Log: Test Code (lines 375-388): async def test_rate_limiting_with_different_operations(self, rate_limit_server):
"""Test that rate limiting applies to all types of operations."""
rate_limit_server.add_middleware(
RateLimitingMiddleware(max_requests_per_second=9.0, burst_capacity=5)
)
async with Client(rate_limit_server) as client:
# Mix different operations
await client.call_tool("quick_action", {"message": "test"})
await client.call_tool("heavy_computation")
# Should be rate limited regardless of operation type
with pytest.raises(ToolError, match="Rate limit exceeded"):
await client.call_tool("batch_process", {"items": ["a", "b", "c"]})Why It's Failing: The rate limiter uses a token bucket algorithm. With
The 5th request (the one expected to fail) is actually within the burst capacity, so it succeeds on Python 3.13. This might work on other Python versions due to timing variations or implementation differences in asyncio. Related Files
|
Component keys are now globally unique by including a type prefix. This eliminates any possibility of key collisions between different component types.
Key prefixes:
tool:for toolsprompt:for promptsresource:for resourcestemplate:for resource templatesLocalProvider now uses unified
_componentsdict storage.get_tasks()returns a flatSequence[FastMCPComponent]instead of the previousTaskComponentsdataclass. Subclasses ofFastMCPComponentthat don't defineKEY_PREFIXwill emit a warning.Closes #2703