fastmcp 2.14.4__py3-none-any.whl → 3.0.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. fastmcp/_vendor/__init__.py +1 -0
  2. fastmcp/_vendor/docket_di/README.md +7 -0
  3. fastmcp/_vendor/docket_di/__init__.py +163 -0
  4. fastmcp/cli/cli.py +112 -28
  5. fastmcp/cli/install/claude_code.py +1 -5
  6. fastmcp/cli/install/claude_desktop.py +1 -5
  7. fastmcp/cli/install/cursor.py +1 -5
  8. fastmcp/cli/install/gemini_cli.py +1 -5
  9. fastmcp/cli/install/mcp_json.py +1 -6
  10. fastmcp/cli/run.py +146 -5
  11. fastmcp/client/__init__.py +7 -9
  12. fastmcp/client/auth/oauth.py +18 -17
  13. fastmcp/client/client.py +100 -870
  14. fastmcp/client/elicitation.py +1 -1
  15. fastmcp/client/mixins/__init__.py +13 -0
  16. fastmcp/client/mixins/prompts.py +295 -0
  17. fastmcp/client/mixins/resources.py +325 -0
  18. fastmcp/client/mixins/task_management.py +157 -0
  19. fastmcp/client/mixins/tools.py +397 -0
  20. fastmcp/client/sampling/handlers/anthropic.py +2 -2
  21. fastmcp/client/sampling/handlers/openai.py +1 -1
  22. fastmcp/client/tasks.py +3 -3
  23. fastmcp/client/telemetry.py +47 -0
  24. fastmcp/client/transports/__init__.py +38 -0
  25. fastmcp/client/transports/base.py +82 -0
  26. fastmcp/client/transports/config.py +170 -0
  27. fastmcp/client/transports/http.py +145 -0
  28. fastmcp/client/transports/inference.py +154 -0
  29. fastmcp/client/transports/memory.py +90 -0
  30. fastmcp/client/transports/sse.py +89 -0
  31. fastmcp/client/transports/stdio.py +543 -0
  32. fastmcp/contrib/component_manager/README.md +4 -10
  33. fastmcp/contrib/component_manager/__init__.py +1 -2
  34. fastmcp/contrib/component_manager/component_manager.py +95 -160
  35. fastmcp/contrib/component_manager/example.py +1 -1
  36. fastmcp/contrib/mcp_mixin/example.py +4 -4
  37. fastmcp/contrib/mcp_mixin/mcp_mixin.py +11 -4
  38. fastmcp/decorators.py +41 -0
  39. fastmcp/dependencies.py +12 -1
  40. fastmcp/exceptions.py +4 -0
  41. fastmcp/experimental/server/openapi/__init__.py +18 -15
  42. fastmcp/mcp_config.py +13 -4
  43. fastmcp/prompts/__init__.py +6 -3
  44. fastmcp/prompts/function_prompt.py +465 -0
  45. fastmcp/prompts/prompt.py +321 -271
  46. fastmcp/resources/__init__.py +5 -3
  47. fastmcp/resources/function_resource.py +335 -0
  48. fastmcp/resources/resource.py +325 -115
  49. fastmcp/resources/template.py +215 -43
  50. fastmcp/resources/types.py +27 -12
  51. fastmcp/server/__init__.py +2 -2
  52. fastmcp/server/auth/__init__.py +14 -0
  53. fastmcp/server/auth/auth.py +30 -10
  54. fastmcp/server/auth/authorization.py +190 -0
  55. fastmcp/server/auth/oauth_proxy/__init__.py +14 -0
  56. fastmcp/server/auth/oauth_proxy/consent.py +361 -0
  57. fastmcp/server/auth/oauth_proxy/models.py +178 -0
  58. fastmcp/server/auth/{oauth_proxy.py → oauth_proxy/proxy.py} +24 -778
  59. fastmcp/server/auth/oauth_proxy/ui.py +277 -0
  60. fastmcp/server/auth/oidc_proxy.py +2 -2
  61. fastmcp/server/auth/providers/auth0.py +24 -94
  62. fastmcp/server/auth/providers/aws.py +26 -95
  63. fastmcp/server/auth/providers/azure.py +41 -129
  64. fastmcp/server/auth/providers/descope.py +18 -49
  65. fastmcp/server/auth/providers/discord.py +25 -86
  66. fastmcp/server/auth/providers/github.py +23 -87
  67. fastmcp/server/auth/providers/google.py +24 -87
  68. fastmcp/server/auth/providers/introspection.py +60 -79
  69. fastmcp/server/auth/providers/jwt.py +30 -67
  70. fastmcp/server/auth/providers/oci.py +47 -110
  71. fastmcp/server/auth/providers/scalekit.py +23 -61
  72. fastmcp/server/auth/providers/supabase.py +18 -47
  73. fastmcp/server/auth/providers/workos.py +34 -127
  74. fastmcp/server/context.py +372 -419
  75. fastmcp/server/dependencies.py +541 -251
  76. fastmcp/server/elicitation.py +20 -18
  77. fastmcp/server/event_store.py +3 -3
  78. fastmcp/server/http.py +16 -6
  79. fastmcp/server/lifespan.py +198 -0
  80. fastmcp/server/low_level.py +92 -2
  81. fastmcp/server/middleware/__init__.py +5 -1
  82. fastmcp/server/middleware/authorization.py +312 -0
  83. fastmcp/server/middleware/caching.py +101 -54
  84. fastmcp/server/middleware/middleware.py +6 -9
  85. fastmcp/server/middleware/ping.py +70 -0
  86. fastmcp/server/middleware/tool_injection.py +2 -2
  87. fastmcp/server/mixins/__init__.py +7 -0
  88. fastmcp/server/mixins/lifespan.py +217 -0
  89. fastmcp/server/mixins/mcp_operations.py +392 -0
  90. fastmcp/server/mixins/transport.py +342 -0
  91. fastmcp/server/openapi/__init__.py +41 -21
  92. fastmcp/server/openapi/components.py +16 -339
  93. fastmcp/server/openapi/routing.py +34 -118
  94. fastmcp/server/openapi/server.py +67 -392
  95. fastmcp/server/providers/__init__.py +71 -0
  96. fastmcp/server/providers/aggregate.py +261 -0
  97. fastmcp/server/providers/base.py +578 -0
  98. fastmcp/server/providers/fastmcp_provider.py +674 -0
  99. fastmcp/server/providers/filesystem.py +226 -0
  100. fastmcp/server/providers/filesystem_discovery.py +327 -0
  101. fastmcp/server/providers/local_provider/__init__.py +11 -0
  102. fastmcp/server/providers/local_provider/decorators/__init__.py +15 -0
  103. fastmcp/server/providers/local_provider/decorators/prompts.py +256 -0
  104. fastmcp/server/providers/local_provider/decorators/resources.py +240 -0
  105. fastmcp/server/providers/local_provider/decorators/tools.py +315 -0
  106. fastmcp/server/providers/local_provider/local_provider.py +465 -0
  107. fastmcp/server/providers/openapi/__init__.py +39 -0
  108. fastmcp/server/providers/openapi/components.py +332 -0
  109. fastmcp/server/providers/openapi/provider.py +405 -0
  110. fastmcp/server/providers/openapi/routing.py +109 -0
  111. fastmcp/server/providers/proxy.py +867 -0
  112. fastmcp/server/providers/skills/__init__.py +59 -0
  113. fastmcp/server/providers/skills/_common.py +101 -0
  114. fastmcp/server/providers/skills/claude_provider.py +44 -0
  115. fastmcp/server/providers/skills/directory_provider.py +153 -0
  116. fastmcp/server/providers/skills/skill_provider.py +432 -0
  117. fastmcp/server/providers/skills/vendor_providers.py +142 -0
  118. fastmcp/server/providers/wrapped_provider.py +140 -0
  119. fastmcp/server/proxy.py +34 -700
  120. fastmcp/server/sampling/run.py +341 -2
  121. fastmcp/server/sampling/sampling_tool.py +4 -3
  122. fastmcp/server/server.py +1214 -2171
  123. fastmcp/server/tasks/__init__.py +2 -1
  124. fastmcp/server/tasks/capabilities.py +13 -1
  125. fastmcp/server/tasks/config.py +66 -3
  126. fastmcp/server/tasks/handlers.py +65 -273
  127. fastmcp/server/tasks/keys.py +4 -6
  128. fastmcp/server/tasks/requests.py +474 -0
  129. fastmcp/server/tasks/routing.py +76 -0
  130. fastmcp/server/tasks/subscriptions.py +20 -11
  131. fastmcp/server/telemetry.py +131 -0
  132. fastmcp/server/transforms/__init__.py +244 -0
  133. fastmcp/server/transforms/namespace.py +193 -0
  134. fastmcp/server/transforms/prompts_as_tools.py +175 -0
  135. fastmcp/server/transforms/resources_as_tools.py +190 -0
  136. fastmcp/server/transforms/tool_transform.py +96 -0
  137. fastmcp/server/transforms/version_filter.py +124 -0
  138. fastmcp/server/transforms/visibility.py +526 -0
  139. fastmcp/settings.py +34 -96
  140. fastmcp/telemetry.py +122 -0
  141. fastmcp/tools/__init__.py +10 -3
  142. fastmcp/tools/function_parsing.py +201 -0
  143. fastmcp/tools/function_tool.py +467 -0
  144. fastmcp/tools/tool.py +215 -362
  145. fastmcp/tools/tool_transform.py +38 -21
  146. fastmcp/utilities/async_utils.py +69 -0
  147. fastmcp/utilities/components.py +152 -91
  148. fastmcp/utilities/inspect.py +8 -20
  149. fastmcp/utilities/json_schema.py +12 -5
  150. fastmcp/utilities/json_schema_type.py +17 -15
  151. fastmcp/utilities/lifespan.py +56 -0
  152. fastmcp/utilities/logging.py +12 -4
  153. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -3
  154. fastmcp/utilities/openapi/parser.py +3 -3
  155. fastmcp/utilities/pagination.py +80 -0
  156. fastmcp/utilities/skills.py +253 -0
  157. fastmcp/utilities/tests.py +0 -16
  158. fastmcp/utilities/timeout.py +47 -0
  159. fastmcp/utilities/types.py +1 -1
  160. fastmcp/utilities/versions.py +285 -0
  161. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/METADATA +8 -5
  162. fastmcp-3.0.0b1.dist-info/RECORD +228 -0
  163. fastmcp/client/transports.py +0 -1170
  164. fastmcp/contrib/component_manager/component_service.py +0 -209
  165. fastmcp/prompts/prompt_manager.py +0 -117
  166. fastmcp/resources/resource_manager.py +0 -338
  167. fastmcp/server/tasks/converters.py +0 -206
  168. fastmcp/server/tasks/protocol.py +0 -359
  169. fastmcp/tools/tool_manager.py +0 -170
  170. fastmcp/utilities/mcp_config.py +0 -56
  171. fastmcp-2.14.4.dist-info/RECORD +0 -161
  172. /fastmcp/server/{openapi → providers/openapi}/README.md +0 -0
  173. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/WHEEL +0 -0
  174. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/entry_points.txt +0 -0
  175. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,59 @@
1
+ """Skills providers for exposing agent skills as MCP resources.
2
+
3
+ This module provides a two-layer architecture for skill discovery:
4
+
5
+ - **SkillProvider**: Handles a single skill folder, exposing its files as resources.
6
+ - **SkillsDirectoryProvider**: Scans a directory, creates a SkillProvider per folder.
7
+ - **Vendor providers**: Platform-specific providers for Claude, Cursor, VS Code, Codex,
8
+ Gemini, Goose, Copilot, and OpenCode.
9
+
10
+ Example:
11
+ ```python
12
+ from pathlib import Path
13
+ from fastmcp import FastMCP
14
+ from fastmcp.server.providers.skills import ClaudeSkillsProvider, SkillProvider
15
+
16
+ mcp = FastMCP("Skills Server")
17
+
18
+ # Load a single skill
19
+ mcp.add_provider(SkillProvider(Path.home() / ".claude/skills/pdf-processing"))
20
+
21
+ # Or load all skills in a directory
22
+ mcp.add_provider(ClaudeSkillsProvider()) # Uses ~/.claude/skills/
23
+ ```
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ # Import providers
29
+ from fastmcp.server.providers.skills.claude_provider import ClaudeSkillsProvider
30
+ from fastmcp.server.providers.skills.directory_provider import SkillsDirectoryProvider
31
+ from fastmcp.server.providers.skills.skill_provider import SkillProvider
32
+ from fastmcp.server.providers.skills.vendor_providers import (
33
+ CodexSkillsProvider,
34
+ CopilotSkillsProvider,
35
+ CursorSkillsProvider,
36
+ GeminiSkillsProvider,
37
+ GooseSkillsProvider,
38
+ OpenCodeSkillsProvider,
39
+ VSCodeSkillsProvider,
40
+ )
41
+
42
+
43
+ # Backwards compatibility alias
44
+ SkillsProvider = SkillsDirectoryProvider
45
+
46
+
47
+ __all__ = [
48
+ "ClaudeSkillsProvider",
49
+ "CodexSkillsProvider",
50
+ "CopilotSkillsProvider",
51
+ "CursorSkillsProvider",
52
+ "GeminiSkillsProvider",
53
+ "GooseSkillsProvider",
54
+ "OpenCodeSkillsProvider",
55
+ "SkillProvider",
56
+ "SkillsDirectoryProvider",
57
+ "SkillsProvider", # Backwards compatibility alias
58
+ "VSCodeSkillsProvider",
59
+ ]
@@ -0,0 +1,101 @@
1
+ """Shared utilities and data structures for skills providers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import re
7
+ from dataclasses import dataclass, field
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+
12
+ @dataclass
13
+ class SkillFileInfo:
14
+ """Information about a file within a skill."""
15
+
16
+ path: str # Relative path within skill directory
17
+ size: int
18
+ hash: str # sha256 hash
19
+
20
+
21
+ @dataclass
22
+ class SkillInfo:
23
+ """Parsed information about a skill."""
24
+
25
+ name: str # Directory name (canonical identifier)
26
+ description: str # From frontmatter or first line
27
+ path: Path # Absolute path to skill directory
28
+ main_file: str # Name of main file (e.g., "SKILL.md")
29
+ files: list[SkillFileInfo] = field(default_factory=list)
30
+ frontmatter: dict[str, Any] = field(default_factory=dict)
31
+
32
+
33
+ def parse_frontmatter(content: str) -> tuple[dict[str, Any], str]:
34
+ """Parse YAML frontmatter from markdown content.
35
+
36
+ Args:
37
+ content: Markdown content potentially starting with ---
38
+
39
+ Returns:
40
+ Tuple of (frontmatter dict, remaining content)
41
+ """
42
+ if not content.startswith("---"):
43
+ return {}, content
44
+
45
+ # Find the closing ---
46
+ end_match = re.search(r"\n---\s*\n", content[3:])
47
+ if not end_match:
48
+ return {}, content
49
+
50
+ frontmatter_text = content[3 : 3 + end_match.start()]
51
+ remaining = content[3 + end_match.end() :]
52
+
53
+ # Parse YAML (simple key: value parsing, no complex types)
54
+ frontmatter: dict[str, Any] = {}
55
+ for line in frontmatter_text.strip().split("\n"):
56
+ if ":" in line:
57
+ key, _, value = line.partition(":")
58
+ key = key.strip()
59
+ value = value.strip()
60
+
61
+ # Handle quoted strings
62
+ if (value.startswith('"') and value.endswith('"')) or (
63
+ value.startswith("'") and value.endswith("'")
64
+ ):
65
+ value = value[1:-1]
66
+
67
+ # Handle lists [a, b, c]
68
+ if value.startswith("[") and value.endswith("]"):
69
+ items = value[1:-1].split(",")
70
+ value = [item.strip().strip("\"'") for item in items if item.strip()]
71
+
72
+ frontmatter[key] = value
73
+
74
+ return frontmatter, remaining
75
+
76
+
77
+ def compute_file_hash(path: Path) -> str:
78
+ """Compute SHA256 hash of a file."""
79
+ sha256 = hashlib.sha256()
80
+ with open(path, "rb") as f:
81
+ for chunk in iter(lambda: f.read(8192), b""):
82
+ sha256.update(chunk)
83
+ return f"sha256:{sha256.hexdigest()}"
84
+
85
+
86
+ def scan_skill_files(skill_dir: Path) -> list[SkillFileInfo]:
87
+ """Scan a skill directory for all files."""
88
+ files = []
89
+ # Sort for deterministic ordering across platforms
90
+ for file_path in sorted(skill_dir.rglob("*")):
91
+ if file_path.is_file():
92
+ rel_path = file_path.relative_to(skill_dir)
93
+ files.append(
94
+ SkillFileInfo(
95
+ # Use POSIX paths for cross-platform URI consistency
96
+ path=rel_path.as_posix(),
97
+ size=file_path.stat().st_size,
98
+ hash=compute_file_hash(file_path),
99
+ )
100
+ )
101
+ return files
@@ -0,0 +1,44 @@
1
+ """Claude-specific skills provider for Claude Code skills."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Literal
7
+
8
+ from fastmcp.server.providers.skills.directory_provider import SkillsDirectoryProvider
9
+
10
+
11
+ class ClaudeSkillsProvider(SkillsDirectoryProvider):
12
+ """Provider for Claude Code skills from ~/.claude/skills/.
13
+
14
+ A convenience subclass that sets the default root to Claude's skills location.
15
+
16
+ Args:
17
+ reload: If True, re-scan on every request. Defaults to False.
18
+ supporting_files: How supporting files are exposed:
19
+ - "template": Accessed via ResourceTemplate, hidden from list_resources().
20
+ - "resources": Each file exposed as individual Resource in list_resources().
21
+
22
+ Example:
23
+ ```python
24
+ from fastmcp import FastMCP
25
+ from fastmcp.server.providers.skills import ClaudeSkillsProvider
26
+
27
+ mcp = FastMCP("Claude Skills")
28
+ mcp.add_provider(ClaudeSkillsProvider()) # Uses default location
29
+ ```
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ reload: bool = False,
35
+ supporting_files: Literal["template", "resources"] = "template",
36
+ ) -> None:
37
+ root = Path.home() / ".claude" / "skills"
38
+
39
+ super().__init__(
40
+ roots=[root],
41
+ reload=reload,
42
+ main_file_name="SKILL.md",
43
+ supporting_files=supporting_files,
44
+ )
@@ -0,0 +1,153 @@
1
+ """Directory scanning provider for discovering multiple skills."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from pathlib import Path
7
+ from typing import Literal
8
+
9
+ from fastmcp.resources.resource import Resource
10
+ from fastmcp.resources.template import ResourceTemplate
11
+ from fastmcp.server.providers.aggregate import AggregateProvider
12
+ from fastmcp.server.providers.skills.skill_provider import SkillProvider
13
+ from fastmcp.utilities.logging import get_logger
14
+ from fastmcp.utilities.versions import VersionSpec
15
+
16
+ logger = get_logger(__name__)
17
+
18
+
19
+ class SkillsDirectoryProvider(AggregateProvider):
20
+ """Provider that scans directories and creates a SkillProvider per skill folder.
21
+
22
+ This extends AggregateProvider to combine multiple SkillProviders into one.
23
+ Each subdirectory containing a main file (default: SKILL.md) becomes a skill.
24
+ Can scan multiple root directories - if a skill name appears in multiple roots,
25
+ the first one found wins.
26
+
27
+ Args:
28
+ roots: Root directory(ies) containing skill folders. Can be a single path
29
+ or a sequence of paths.
30
+ reload: If True, re-discover skills on each request. Defaults to False.
31
+ main_file_name: Name of the main skill file. Defaults to "SKILL.md".
32
+ supporting_files: How supporting files are exposed in child SkillProviders:
33
+ - "template": Accessed via ResourceTemplate, hidden from list_resources().
34
+ - "resources": Each file exposed as individual Resource in list_resources().
35
+
36
+ Example:
37
+ ```python
38
+ from pathlib import Path
39
+ from fastmcp import FastMCP
40
+ from fastmcp.server.providers.skills import SkillsDirectoryProvider
41
+
42
+ mcp = FastMCP("Skills")
43
+ # Single directory
44
+ mcp.add_provider(SkillsDirectoryProvider(
45
+ roots=Path.home() / ".claude" / "skills",
46
+ reload=True, # Re-scan on each request
47
+ ))
48
+ # Multiple directories
49
+ mcp.add_provider(SkillsDirectoryProvider(
50
+ roots=[Path("/etc/skills"), Path.home() / ".local" / "skills"],
51
+ ))
52
+ ```
53
+ """
54
+
55
+ def __init__(
56
+ self,
57
+ roots: str | Path | Sequence[str | Path],
58
+ reload: bool = False,
59
+ main_file_name: str = "SKILL.md",
60
+ supporting_files: Literal["template", "resources"] = "template",
61
+ ) -> None:
62
+ super().__init__()
63
+ # Normalize to sequence: single path becomes list
64
+ if isinstance(roots, (str, Path)):
65
+ roots = [roots]
66
+
67
+ self._roots = [Path(r).resolve() for r in roots]
68
+ self._reload = reload
69
+ self._main_file_name = main_file_name
70
+ self._supporting_files = supporting_files
71
+ self._discovered = False
72
+
73
+ # Discover skills at init
74
+ self._discover_skills()
75
+
76
+ def _discover_skills(self) -> None:
77
+ """Scan root directories and create SkillProvider per valid skill folder."""
78
+ # Clear existing providers if reloading
79
+ self.providers.clear()
80
+
81
+ seen_skill_names: set[str] = set()
82
+
83
+ for root in self._roots:
84
+ if not root.exists():
85
+ logger.debug(f"Skills root does not exist: {root}")
86
+ continue
87
+
88
+ for skill_dir in root.iterdir():
89
+ if not skill_dir.is_dir():
90
+ continue
91
+
92
+ main_file = skill_dir / self._main_file_name
93
+ if not main_file.exists():
94
+ continue
95
+
96
+ skill_name = skill_dir.name
97
+ # Skip if we've already seen this skill name (first wins)
98
+ if skill_name in seen_skill_names:
99
+ logger.debug(
100
+ f"Skipping duplicate skill '{skill_name}' from {root} "
101
+ f"(already found in earlier root)"
102
+ )
103
+ continue
104
+
105
+ try:
106
+ provider = SkillProvider(
107
+ skill_path=skill_dir,
108
+ main_file_name=self._main_file_name,
109
+ supporting_files=self._supporting_files,
110
+ )
111
+ self.providers.append(provider)
112
+ seen_skill_names.add(skill_name)
113
+ except (FileNotFoundError, PermissionError, OSError):
114
+ logger.exception(f"Failed to load skill: {skill_dir.name}")
115
+
116
+ self._discovered = True
117
+ logger.debug(
118
+ f"SkillsDirectoryProvider loaded {len(self.providers)} skills "
119
+ f"from {len(self._roots)} root(s)"
120
+ )
121
+
122
+ async def _ensure_discovered(self) -> None:
123
+ """Ensure skills are discovered, rediscovering if reload is enabled."""
124
+ if self._reload or not self._discovered:
125
+ self._discover_skills()
126
+
127
+ # Override list methods to support reload
128
+ async def _list_resources(self) -> Sequence[Resource]:
129
+ await self._ensure_discovered()
130
+ return await super()._list_resources()
131
+
132
+ async def _list_resource_templates(self) -> Sequence[ResourceTemplate]:
133
+ await self._ensure_discovered()
134
+ return await super()._list_resource_templates()
135
+
136
+ async def _get_resource(
137
+ self, uri: str, version: VersionSpec | None = None
138
+ ) -> Resource | None:
139
+ await self._ensure_discovered()
140
+ return await super()._get_resource(uri, version)
141
+
142
+ async def _get_resource_template(
143
+ self, uri: str, version: VersionSpec | None = None
144
+ ) -> ResourceTemplate | None:
145
+ await self._ensure_discovered()
146
+ return await super()._get_resource_template(uri, version)
147
+
148
+ def __repr__(self) -> str:
149
+ roots_repr = self._roots[0] if len(self._roots) == 1 else self._roots
150
+ return (
151
+ f"SkillsDirectoryProvider(roots={roots_repr!r}, "
152
+ f"reload={self._reload}, skills={len(self.providers)})"
153
+ )