mirror of
https://github.com/HKUDS/OpenSpace.git
synced 2026-10-06 02:47:50 +00:00
mcp: keep local skill search lightweight
This commit is contained in:
parent
ccda314d34
commit
f4451aa0ac
2 changed files with 113 additions and 1 deletions
|
|
@ -119,6 +119,7 @@ mcp = FastMCP("OpenSpace", **_fastmcp_kwargs)
|
|||
_openspace_instance = None
|
||||
_openspace_lock = asyncio.Lock()
|
||||
_standalone_store = None
|
||||
_local_skill_registry = None
|
||||
|
||||
# Internal state: tracks bot skill directories already registered this session.
|
||||
_registered_skill_dirs: set = set()
|
||||
|
|
@ -198,6 +199,64 @@ def _get_store():
|
|||
return _standalone_store
|
||||
|
||||
|
||||
def _get_local_skill_registry():
|
||||
"""Build a lightweight SkillRegistry for local-only skill search.
|
||||
|
||||
This avoids initializing the full OpenSpace engine when callers only
|
||||
want to inspect local skills. It mirrors the skill directory discovery
|
||||
order used by the full engine, but skips LLM / provider startup.
|
||||
"""
|
||||
global _local_skill_registry
|
||||
if _local_skill_registry is not None:
|
||||
return _local_skill_registry
|
||||
|
||||
from openspace.config import get_config
|
||||
from openspace.skill_engine import SkillRegistry
|
||||
|
||||
skill_paths: List[Path] = []
|
||||
|
||||
host_dirs_raw = os.environ.get("OPENSPACE_HOST_SKILL_DIRS", "")
|
||||
if host_dirs_raw:
|
||||
for d in host_dirs_raw.split(","):
|
||||
d = d.strip()
|
||||
if not d:
|
||||
continue
|
||||
p = Path(d)
|
||||
if p.exists():
|
||||
skill_paths.append(p)
|
||||
else:
|
||||
logger.warning("Host skill dir does not exist: %s", d)
|
||||
|
||||
try:
|
||||
skill_cfg = get_config().skills
|
||||
except Exception as e:
|
||||
logger.warning("Failed to load local skill config: %s", e)
|
||||
skill_cfg = None
|
||||
|
||||
if skill_cfg and skill_cfg.skill_dirs:
|
||||
for d in skill_cfg.skill_dirs:
|
||||
p = Path(d)
|
||||
if p in skill_paths:
|
||||
continue
|
||||
if p.exists():
|
||||
skill_paths.append(p)
|
||||
else:
|
||||
logger.warning("Configured skill dir does not exist: %s", d)
|
||||
|
||||
builtin_skills = Path(__file__).resolve().parent / "skills"
|
||||
if builtin_skills.exists():
|
||||
skill_paths.append(builtin_skills)
|
||||
|
||||
if not skill_paths:
|
||||
logger.debug("No local skill directories found")
|
||||
return None
|
||||
|
||||
registry = SkillRegistry(skill_dirs=skill_paths)
|
||||
registry.discover()
|
||||
_local_skill_registry = registry
|
||||
return registry
|
||||
|
||||
|
||||
def _get_cloud_client():
|
||||
"""Get a OpenSpaceClient instance (raises CloudError if not configured)."""
|
||||
from openspace.cloud.auth import get_openspace_auth
|
||||
|
|
@ -597,7 +656,11 @@ async def search_skills(
|
|||
# Re-scan host skill directories so newly created skills are searchable.
|
||||
local_skills = None
|
||||
store = None
|
||||
if source in ("all", "local"):
|
||||
if source == "local":
|
||||
registry = _get_local_skill_registry()
|
||||
if registry:
|
||||
local_skills = registry.list_skills()
|
||||
elif source == "all":
|
||||
openspace = await _get_openspace()
|
||||
|
||||
host_skill_dirs_raw = os.environ.get("OPENSPACE_HOST_SKILL_DIRS", "")
|
||||
|
|
|
|||
49
tests/test_issue3_startup.py
Normal file
49
tests/test_issue3_startup.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from openspace import mcp_server
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_local_search_skills_does_not_initialize_openspace(monkeypatch, tmp_path):
|
||||
skill_root = tmp_path / "skills"
|
||||
skill_dir = skill_root / "demo-local-skill"
|
||||
skill_dir.mkdir(parents=True)
|
||||
(skill_dir / "SKILL.md").write_text(
|
||||
"""---
|
||||
name: Demo Local Skill
|
||||
description: Local regression test skill
|
||||
---
|
||||
|
||||
Find me when querying demo local skill.
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
async def forbidden_get_openspace():
|
||||
pytest.fail("_get_openspace should not run for source='local'")
|
||||
|
||||
monkeypatch.setenv("OPENSPACE_HOST_SKILL_DIRS", str(skill_root))
|
||||
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
||||
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
||||
monkeypatch.delenv("OPENAI_BASE_URL", raising=False)
|
||||
monkeypatch.setattr(mcp_server, "_get_openspace", forbidden_get_openspace)
|
||||
monkeypatch.setattr(
|
||||
"openspace.cloud.embedding.generate_embedding",
|
||||
lambda text, api_key=None: None,
|
||||
)
|
||||
|
||||
response = await mcp_server.search_skills(
|
||||
query="demo local skill",
|
||||
source="local",
|
||||
limit=5,
|
||||
auto_import=False,
|
||||
)
|
||||
|
||||
payload = json.loads(response)
|
||||
assert payload["count"] >= 1
|
||||
assert any(
|
||||
item["name"] == "Demo Local Skill"
|
||||
for item in payload["results"]
|
||||
)
|
||||
Loading…
Add table
Reference in a new issue