mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-27 15:51:33 +02:00
Preserve skill metadata through provider wrapping (#3237)
* Preserve computed _meta through provider wrapping; add skill identity to _meta * chore: Update SDK documentation --------- Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>
This commit is contained in:
parent
390a11d7d2
commit
aa7946d353
4 changed files with 95 additions and 13 deletions
|
|
@ -18,7 +18,13 @@ A resource representing a skill's main file or manifest.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `read` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L41" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_meta` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L41" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_meta(self) -> dict[str, Any]
|
||||
```
|
||||
|
||||
#### `read` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L50" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
read(self) -> str | bytes | ResourceResult
|
||||
|
|
@ -27,7 +33,7 @@ read(self) -> str | bytes | ResourceResult
|
|||
Read the resource content.
|
||||
|
||||
|
||||
### `SkillFileTemplate` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L61" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `SkillFileTemplate` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L70" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A template for accessing files within a skill.
|
||||
|
|
@ -35,7 +41,7 @@ A template for accessing files within a skill.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `read` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L66" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `read` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L75" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
read(self, arguments: dict[str, Any]) -> str | bytes | ResourceResult
|
||||
|
|
@ -44,7 +50,7 @@ read(self, arguments: dict[str, Any]) -> str | bytes | ResourceResult
|
|||
Read a file from the skill directory.
|
||||
|
||||
|
||||
#### `create_resource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L106" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `create_resource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L115" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
create_resource(self, uri: str, params: dict[str, Any]) -> Resource
|
||||
|
|
@ -56,7 +62,7 @@ Note: This is not typically used since _read() handles file reading directly.
|
|||
Provided for compatibility with the ResourceTemplate interface.
|
||||
|
||||
|
||||
### `SkillFileResource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L132" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `SkillFileResource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L141" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A resource representing a specific file within a skill.
|
||||
|
|
@ -64,7 +70,13 @@ A resource representing a specific file within a skill.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `read` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L138" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_meta` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L147" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_meta(self) -> dict[str, Any]
|
||||
```
|
||||
|
||||
#### `read` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L155" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
read(self) -> str | bytes | ResourceResult
|
||||
|
|
@ -73,7 +85,7 @@ read(self) -> str | bytes | ResourceResult
|
|||
Read the file content.
|
||||
|
||||
|
||||
### `SkillProvider` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L162" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `SkillProvider` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L179" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Provider that exposes a single skill folder as MCP resources.
|
||||
|
|
@ -99,7 +111,7 @@ manifest) are exposed to clients\:
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `skill_info` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L254" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `skill_info` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/skills/skill_provider.py#L271" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
skill_info(self) -> SkillInfo
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ class FastMCPProviderTool(Tool):
|
|||
tags=tool.tags,
|
||||
annotations=tool.annotations,
|
||||
task_config=tool.task_config,
|
||||
meta=tool.meta,
|
||||
meta=tool.get_meta(),
|
||||
title=tool.title,
|
||||
icons=tool.icons,
|
||||
)
|
||||
|
|
@ -186,7 +186,7 @@ class FastMCPProviderResource(Resource):
|
|||
tags=resource.tags,
|
||||
annotations=resource.annotations,
|
||||
task_config=resource.task_config,
|
||||
meta=resource.meta,
|
||||
meta=resource.get_meta(),
|
||||
title=resource.title,
|
||||
icons=resource.icons,
|
||||
)
|
||||
|
|
@ -255,7 +255,7 @@ class FastMCPProviderPrompt(Prompt):
|
|||
arguments=prompt.arguments,
|
||||
tags=prompt.tags,
|
||||
task_config=prompt.task_config,
|
||||
meta=prompt.meta,
|
||||
meta=prompt.get_meta(),
|
||||
title=prompt.title,
|
||||
icons=prompt.icons,
|
||||
)
|
||||
|
|
@ -359,7 +359,7 @@ class FastMCPProviderResourceTemplate(ResourceTemplate):
|
|||
tags=template.tags,
|
||||
annotations=template.annotations,
|
||||
task_config=template.task_config,
|
||||
meta=template.meta,
|
||||
meta=template.get_meta(),
|
||||
title=template.title,
|
||||
icons=template.icons,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import json
|
|||
import mimetypes
|
||||
from collections.abc import Sequence
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
from typing import Any, Literal, cast
|
||||
|
||||
from pydantic import AnyUrl
|
||||
|
||||
|
|
@ -38,6 +38,15 @@ class SkillResource(Resource):
|
|||
skill_info: SkillInfo
|
||||
is_manifest: bool = False
|
||||
|
||||
def get_meta(self) -> dict[str, Any]:
|
||||
meta = super().get_meta()
|
||||
fastmcp = cast(dict[str, Any], meta["fastmcp"])
|
||||
fastmcp["skill"] = {
|
||||
"name": self.skill_info.name,
|
||||
"is_manifest": self.is_manifest,
|
||||
}
|
||||
return meta
|
||||
|
||||
async def read(self) -> str | bytes | ResourceResult:
|
||||
"""Read the resource content."""
|
||||
if self.is_manifest:
|
||||
|
|
@ -135,6 +144,14 @@ class SkillFileResource(Resource):
|
|||
skill_info: SkillInfo
|
||||
file_path: str
|
||||
|
||||
def get_meta(self) -> dict[str, Any]:
|
||||
meta = super().get_meta()
|
||||
fastmcp = cast(dict[str, Any], meta["fastmcp"])
|
||||
fastmcp["skill"] = {
|
||||
"name": self.skill_info.name,
|
||||
}
|
||||
return meta
|
||||
|
||||
async def read(self) -> str | bytes | ResourceResult:
|
||||
"""Read the file content."""
|
||||
full_path = self.skill_info.path / self.file_path
|
||||
|
|
|
|||
|
|
@ -187,6 +187,59 @@ This is my skill content.
|
|||
result = await client.read_resource(AnyUrl("skill://my-skill/reference.md"))
|
||||
assert "# Reference" in result[0].text
|
||||
|
||||
async def test_skill_resource_meta(self, single_skill_dir: Path):
|
||||
"""SkillResource populates meta with skill name and is_manifest."""
|
||||
provider = SkillProvider(skill_path=single_skill_dir)
|
||||
resources = await provider.list_resources()
|
||||
|
||||
by_name = {r.name: r for r in resources}
|
||||
|
||||
main_meta = by_name["my-skill/SKILL.md"].get_meta()
|
||||
assert main_meta["fastmcp"]["skill"] == {
|
||||
"name": "my-skill",
|
||||
"is_manifest": False,
|
||||
}
|
||||
|
||||
manifest_meta = by_name["my-skill/_manifest"].get_meta()
|
||||
assert manifest_meta["fastmcp"]["skill"] == {
|
||||
"name": "my-skill",
|
||||
"is_manifest": True,
|
||||
}
|
||||
|
||||
async def test_skill_file_resource_meta(self, single_skill_dir: Path):
|
||||
"""SkillFileResource populates meta with skill name."""
|
||||
provider = SkillProvider(
|
||||
skill_path=single_skill_dir, supporting_files="resources"
|
||||
)
|
||||
resources = await provider.list_resources()
|
||||
|
||||
by_name = {r.name: r for r in resources}
|
||||
file_meta = by_name["my-skill/reference.md"].get_meta()
|
||||
assert file_meta["fastmcp"]["skill"] == {"name": "my-skill"}
|
||||
|
||||
async def test_skill_meta_survives_mounting(self, single_skill_dir: Path):
|
||||
"""Skill metadata in _meta is preserved when accessed through a mounted server."""
|
||||
child = FastMCP("child")
|
||||
child.add_provider(SkillProvider(skill_path=single_skill_dir))
|
||||
|
||||
parent = FastMCP("parent")
|
||||
parent.mount(child, "skills")
|
||||
|
||||
resources = await parent.list_resources()
|
||||
by_name = {r.name: r for r in resources}
|
||||
|
||||
main_meta = by_name["my-skill/SKILL.md"].get_meta()
|
||||
assert main_meta["fastmcp"]["skill"] == {
|
||||
"name": "my-skill",
|
||||
"is_manifest": False,
|
||||
}
|
||||
|
||||
manifest_meta = by_name["my-skill/_manifest"].get_meta()
|
||||
assert manifest_meta["fastmcp"]["skill"] == {
|
||||
"name": "my-skill",
|
||||
"is_manifest": True,
|
||||
}
|
||||
|
||||
|
||||
class TestSkillsDirectoryProvider:
|
||||
"""Tests for SkillsDirectoryProvider - scans directory for skill folders."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue