feat(mcp): configure enabled tool groups

Add OBSIDIAN_MCP_ENABLED_TOOLS as the service config source for MCP tool groups while keeping OBSIDIAN_MCP_ENABLE_INGEST as a legacy fallback.

Add mcp_service.sh tools list/enable/disable commands, preserve enabled tools across normal start and token rotation, and let start --enable-ingest/--disable-ingest update the same config file.

Teach the MCP runtime to derive file-assets and ingest registration from enabled tool groups so file asset tools can be disabled separately from ingest.

Validation: python3 -m unittest discover -v; python3 -m py_compile scripts/manage_mcp_service.py obsidian_wiki/mcp_server.py tests/test_mcp_service.py tests/test_mcp_server_remote.py; CLI tools enable/disable dry run; git diff --check.
This commit is contained in:
Codex
2026-07-05 15:47:16 +00:00
parent a1ca05f545
commit 937d9a002f
7 changed files with 502 additions and 102 deletions
+55
View File
@@ -78,6 +78,20 @@ class RemoteMcpServerTests(unittest.TestCase):
with patch.dict(os.environ, {"OBSIDIAN_MCP_PORT": "not-an-int"}, clear=True):
self.assertEqual(mcp_server.runtime_config_int("OBSIDIAN_MCP_PORT", 8765), 8765)
def test_enabled_tools_config_prefers_tool_list_over_legacy_ingest_flag(self):
with patch.dict(
os.environ,
{
"OBSIDIAN_MCP_ENABLED_TOOLS": "file-assets",
"OBSIDIAN_MCP_ENABLE_INGEST": "1",
},
clear=True,
):
self.assertEqual(mcp_server.enabled_tool_names(), {"file-assets"})
with patch.dict(os.environ, {"OBSIDIAN_MCP_ENABLE_INGEST": "1"}, clear=True):
self.assertEqual(mcp_server.enabled_tool_names(), {"file-assets", "ingest"})
def test_create_http_app_wraps_streamable_http_app_with_auth(self):
class FakeServer:
def __init__(self):
@@ -188,6 +202,47 @@ class RemoteMcpServerTests(unittest.TestCase):
self.assertIn("cancel_ingest_job", FakeMCP.tool_names)
self.assertIn("check_ingest_opencode_readiness", FakeMCP.tool_names)
def test_create_server_can_disable_file_asset_tools(self):
class FakeMCP:
tool_names = []
prompt_names = []
def __init__(self, _name, **_kwargs):
pass
def tool(self):
def decorator(func):
self.tool_names.append(func.__name__)
return func
return decorator
def resource(self, *_args, **_kwargs):
def decorator(func):
return func
return decorator
def prompt(self, name=None, **_kwargs):
def decorator(func):
self.prompt_names.append(name or func.__name__)
return func
return decorator
with (
tempfile.TemporaryDirectory() as tmp,
patch.dict("sys.modules", {"mcp.server.fastmcp": type("Mod", (), {"FastMCP": FakeMCP})()}),
):
server = mcp_server.create_server(vault_path=Path(tmp), enable_file_assets=False)
self.assertIsInstance(server, FakeMCP)
self.assertIn("file_index_status", FakeMCP.tool_names)
self.assertNotIn("search_file_assets", FakeMCP.tool_names)
self.assertNotIn("list_file_assets", FakeMCP.tool_names)
self.assertNotIn("get_file_asset_metadata", FakeMCP.tool_names)
self.assertNotIn("how_to_use_wiki_file_assets", FakeMCP.prompt_names)
def test_run_http_server_fails_before_server_creation_without_token(self):
with tempfile.TemporaryDirectory() as tmp:
with patch.dict(os.environ, {}, clear=True):