mirror of
https://github.com/HKUDS/OpenSpace.git
synced 2026-08-28 05:15:00 +00:00
fix(mcp): keep local skill search lightweight
This commit is contained in:
parent
38277815ed
commit
11b3e817ae
3 changed files with 128 additions and 15 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -62,6 +62,11 @@ tests/skill_engine/evolution/*
|
|||
!tests/cloud/
|
||||
tests/cloud/*
|
||||
!tests/cloud/test_upload_trust.py
|
||||
!tests/entrypoints/
|
||||
tests/entrypoints/*
|
||||
!tests/entrypoints/mcp/
|
||||
tests/entrypoints/mcp/*
|
||||
!tests/entrypoints/mcp/test_server_search.py
|
||||
scripts/
|
||||
|
||||
# Local agent/project memory
|
||||
|
|
|
|||
|
|
@ -231,6 +231,46 @@ async def _get_runtime_store(*, required: bool = True):
|
|||
return None
|
||||
|
||||
|
||||
async def _get_local_search_context():
|
||||
"""Return local skills without booting the full agent runtime for search.
|
||||
|
||||
``search_skills`` is a discovery-only MCP tool. Initializing OpenSpace here
|
||||
also initializes grounding providers, memory, evolution, and model config,
|
||||
which can take longer than a normal MCP tool timeout on Windows. Reuse the
|
||||
runtime registry when it already exists; otherwise build the same canonical
|
||||
registry directly and omit optional quality-store enrichment.
|
||||
"""
|
||||
|
||||
if _openspace_instance is not None and _openspace_instance.is_initialized():
|
||||
host_skill_dirs_raw = os.environ.get("OPENSPACE_HOST_SKILL_DIRS", "")
|
||||
if host_skill_dirs_raw:
|
||||
env_dirs = [
|
||||
directory.strip()
|
||||
for directory in host_skill_dirs_raw.split(",")
|
||||
if directory.strip()
|
||||
]
|
||||
if env_dirs:
|
||||
await _auto_register_skill_dirs(env_dirs)
|
||||
|
||||
registry = _openspace_instance.get_skill_registry()
|
||||
if not registry:
|
||||
return None, None
|
||||
store = _openspace_instance.get_skill_store()
|
||||
if store and getattr(store, "_closed", False):
|
||||
store = None
|
||||
return registry.list_skills(), store
|
||||
|
||||
from openspace.runtime.skill_registry import build_skill_registry
|
||||
|
||||
registry = await asyncio.to_thread(
|
||||
build_skill_registry,
|
||||
workspace_dir=os.environ.get("OPENSPACE_WORKSPACE"),
|
||||
)
|
||||
if not registry:
|
||||
return None, None
|
||||
return registry.list_skills(), None
|
||||
|
||||
|
||||
async def _get_cloud_mapping_store():
|
||||
from openspace.cloud.local_mapping import CloudLocalMappingStore
|
||||
|
||||
|
|
@ -818,21 +858,7 @@ async def search_skills(
|
|||
if not q:
|
||||
return _json_ok({"results": [], "count": 0})
|
||||
|
||||
# Re-scan host skill directories so newly created skills are searchable.
|
||||
local_skills = None
|
||||
store = None
|
||||
openspace = await _get_openspace()
|
||||
|
||||
host_skill_dirs_raw = os.environ.get("OPENSPACE_HOST_SKILL_DIRS", "")
|
||||
if host_skill_dirs_raw:
|
||||
env_dirs = [d.strip() for d in host_skill_dirs_raw.split(",") if d.strip()]
|
||||
if env_dirs:
|
||||
await _auto_register_skill_dirs(env_dirs)
|
||||
|
||||
registry = openspace.get_skill_registry()
|
||||
if registry:
|
||||
local_skills = registry.list_skills()
|
||||
store = await _get_runtime_store(required=False)
|
||||
local_skills, store = await _get_local_search_context()
|
||||
|
||||
results = await hybrid_search_skills(
|
||||
query=q,
|
||||
|
|
|
|||
82
tests/entrypoints/mcp/test_server_search.py
Normal file
82
tests/entrypoints/mcp/test_server_search.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from openspace.entrypoints.mcp import server
|
||||
|
||||
|
||||
class _Registry:
|
||||
def __init__(self, skills):
|
||||
self._skills = skills
|
||||
|
||||
def list_skills(self):
|
||||
return self._skills
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_local_search_context_builds_registry_without_full_runtime(
|
||||
monkeypatch,
|
||||
):
|
||||
expected = [SimpleNamespace(skill_id="skill-1")]
|
||||
calls = []
|
||||
|
||||
def build_registry(*, workspace_dir=None):
|
||||
calls.append(workspace_dir)
|
||||
return _Registry(expected)
|
||||
|
||||
async def fail_if_full_runtime_starts():
|
||||
raise AssertionError(
|
||||
"local skill search must not initialize OpenSpace"
|
||||
)
|
||||
|
||||
monkeypatch.setattr(server, "_openspace_instance", None)
|
||||
monkeypatch.setattr(server, "_get_openspace", fail_if_full_runtime_starts)
|
||||
monkeypatch.setenv("OPENSPACE_WORKSPACE", "test-workspace")
|
||||
monkeypatch.setattr(
|
||||
"openspace.runtime.skill_registry.build_skill_registry",
|
||||
build_registry,
|
||||
)
|
||||
|
||||
skills, store = await server._get_local_search_context()
|
||||
|
||||
assert skills == expected
|
||||
assert store is None
|
||||
assert calls == ["test-workspace"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_local_search_context_reuses_initialized_runtime(monkeypatch):
|
||||
expected = [SimpleNamespace(skill_id="skill-1")]
|
||||
store = SimpleNamespace(_closed=False)
|
||||
runtime = SimpleNamespace(
|
||||
is_initialized=lambda: True,
|
||||
get_skill_registry=lambda: _Registry(expected),
|
||||
get_skill_store=lambda: store,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(server, "_openspace_instance", runtime)
|
||||
monkeypatch.delenv("OPENSPACE_HOST_SKILL_DIRS", raising=False)
|
||||
|
||||
skills, actual_store = await server._get_local_search_context()
|
||||
|
||||
assert skills == expected
|
||||
assert actual_store is store
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_local_search_context_ignores_closed_runtime_store(monkeypatch):
|
||||
runtime = SimpleNamespace(
|
||||
is_initialized=lambda: True,
|
||||
get_skill_registry=lambda: _Registry([]),
|
||||
get_skill_store=lambda: SimpleNamespace(_closed=True),
|
||||
)
|
||||
|
||||
monkeypatch.setattr(server, "_openspace_instance", runtime)
|
||||
monkeypatch.delenv("OPENSPACE_HOST_SKILL_DIRS", raising=False)
|
||||
|
||||
skills, store = await server._get_local_search_context()
|
||||
|
||||
assert skills == []
|
||||
assert store is None
|
||||
Loading…
Add table
Reference in a new issue