swapped docs symlink
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import uvicorn
|
||||
|
||||
from .config import get_settings
|
||||
|
||||
|
||||
def main(cli: bool = True) -> None:
|
||||
"""Run the root MCP server."""
|
||||
settings = get_settings(cli=cli)
|
||||
uvicorn.run(
|
||||
"personal_mcp.web.app:create_app",
|
||||
factory=True,
|
||||
host=settings.host,
|
||||
port=settings.port,
|
||||
reload=settings.reload,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -8,7 +8,7 @@ from pydantic_settings import BaseSettings
|
||||
from pydantic_settings import SettingsConfigDict
|
||||
|
||||
DEFAULT_ENV_FILE = Path(".env").resolve()
|
||||
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
DEFAULT_SITE_DIR = Path("site").resolve()
|
||||
|
||||
|
||||
class Mounts(BaseModel):
|
||||
@@ -23,17 +23,21 @@ class Settings(BaseSettings):
|
||||
env_file=DEFAULT_ENV_FILE,
|
||||
env_prefix="PERSONAL_MCP_",
|
||||
extra="ignore",
|
||||
cli_implicit_flags=True,
|
||||
)
|
||||
|
||||
debug: bool = False
|
||||
log_level: str = "info"
|
||||
mounts: Mounts = Field(default_factory=Mounts)
|
||||
site_dir: DirectoryPath = Field(default=_REPO_ROOT / "site")
|
||||
site_dir: DirectoryPath = Field(default=DEFAULT_SITE_DIR)
|
||||
host: str = "localhost"
|
||||
port: int = 8080
|
||||
reload: bool = True
|
||||
|
||||
|
||||
@cache
|
||||
def get_settings(**overrides) -> Settings:
|
||||
return Settings(**overrides)
|
||||
def get_settings(*, cli: bool = False, **overrides) -> Settings:
|
||||
return Settings(**overrides, _cli_parse_args=cli) # pyright: ignore[reportCallIssue]
|
||||
|
||||
|
||||
def refresh_settings(**overrides):
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../docs
|
||||
@@ -1,20 +0,0 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
from personal_mcp.mcp import create_mcp
|
||||
from personal_mcp.web.app import create_app as create_fastapi
|
||||
|
||||
__all__ = ["create_app", "main"]
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
"""Create the HTTP application for ASGI servers using factory mode."""
|
||||
return create_fastapi()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the root MCP server."""
|
||||
create_mcp().run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,20 +1,34 @@
|
||||
from pathlib import Path
|
||||
from contextlib import ExitStack
|
||||
from importlib.resources import as_file
|
||||
from importlib.resources import files
|
||||
from weakref import finalize
|
||||
|
||||
from fastmcp.server.providers.skills import SkillsDirectoryProvider
|
||||
|
||||
|
||||
def create_skills_provider() -> SkillsDirectoryProvider:
|
||||
"""Create the provider for skills packaged with personal-mcp."""
|
||||
skills_root = Path(__file__).resolve().parents[1] / "docs" / "skills"
|
||||
if not skills_root.is_dir():
|
||||
raise FileNotFoundError(f"packaged skills directory does not exist: {skills_root}")
|
||||
skills_resource = files("personal_mcp").joinpath("docs", "skills")
|
||||
if not skills_resource.is_dir():
|
||||
raise FileNotFoundError(f"packaged skills directory does not exist: {skills_resource}")
|
||||
|
||||
has_skills = any(skill_dir.is_dir() and (skill_dir / "SKILL.md").is_file() for skill_dir in skills_root.iterdir())
|
||||
has_skills = any(
|
||||
skill_dir.is_dir() and skill_dir.joinpath("SKILL.md").is_file() for skill_dir in skills_resource.iterdir()
|
||||
)
|
||||
if not has_skills:
|
||||
raise ValueError(f"packaged skills directory contains no skills: {skills_root}")
|
||||
raise ValueError(f"packaged skills directory contains no skills: {skills_resource}")
|
||||
|
||||
return SkillsDirectoryProvider(
|
||||
resources = ExitStack()
|
||||
try:
|
||||
skills_root = resources.enter_context(as_file(skills_resource))
|
||||
provider = SkillsDirectoryProvider(
|
||||
roots=skills_root,
|
||||
reload=False,
|
||||
supporting_files="template",
|
||||
)
|
||||
except BaseException:
|
||||
resources.close()
|
||||
raise
|
||||
|
||||
finalize(provider, resources.close)
|
||||
return provider
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from pathlib import Path
|
||||
from importlib.resources import files
|
||||
from importlib.resources.abc import Traversable
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
@@ -11,7 +12,15 @@ from personal_mcp.skills import create_skills_provider
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
SKILLS_ROOT = Path(__file__).parents[2] / "docs" / "skills"
|
||||
SKILLS_ROOT = files("personal_mcp").joinpath("docs", "skills")
|
||||
|
||||
|
||||
def skill_directories() -> list[Traversable]:
|
||||
return [
|
||||
directory
|
||||
for directory in SKILLS_ROOT.iterdir()
|
||||
if directory.is_dir() and directory.joinpath("SKILL.md").is_file()
|
||||
]
|
||||
|
||||
|
||||
class TestSkillsProvider:
|
||||
@@ -20,7 +29,7 @@ class TestSkillsProvider:
|
||||
@pytest.mark.asyncio
|
||||
async def test_discovers_authored_skills(self) -> None:
|
||||
"""Ensures each authored skill is exposed with its description."""
|
||||
expected_names = {path.parent.name for path in SKILLS_ROOT.glob("*/SKILL.md")}
|
||||
expected_names = {directory.name for directory in skill_directories()}
|
||||
mcp = FastMCP("skills-test")
|
||||
mcp.add_provider(create_skills_provider())
|
||||
|
||||
@@ -48,10 +57,11 @@ class TestSkillsProvider:
|
||||
|
||||
def test_frontmatter_names_match_directories(self) -> None:
|
||||
"""Ensures provider identity and authored skill names remain aligned."""
|
||||
for skill_file in SKILLS_ROOT.glob("*/SKILL.md"):
|
||||
for directory in skill_directories():
|
||||
skill_file = directory.joinpath("SKILL.md")
|
||||
raw = skill_file.read_text(encoding="utf-8")
|
||||
frontmatter = yaml.safe_load(raw.split("---", 2)[1])
|
||||
|
||||
assert set(frontmatter) == {"name", "description"}
|
||||
assert frontmatter["name"] == skill_file.parent.name
|
||||
assert frontmatter["name"] == directory.name
|
||||
assert frontmatter["description"]
|
||||
|
||||
Reference in New Issue
Block a user