diff --git a/obsidian_wiki/mcp_server.py b/obsidian_wiki/mcp_server.py index 1a463cb..71407f7 100644 --- a/obsidian_wiki/mcp_server.py +++ b/obsidian_wiki/mcp_server.py @@ -151,6 +151,32 @@ def create_server( ) ) + @server.tool() + def list_file_assets( + doc_type: str | None = None, + source_type: str | None = None, + product_line: str | None = None, + customer: str | None = None, + competitor: str | None = None, + limit: int = 10, + ) -> list[dict[str, Any]]: + """List safe source files matching optional filters. Use search_file_assets when the user gives keywords such as product codes, sizes, or drawing names.""" + return _as_mcp_results( + core_search_file_assets( + vault, + "", + db_path=db, + limit=limit, + doc_type=doc_type, + source_type=source_type, + product_line=product_line, + customer=customer, + competitor=competitor, + profile_name=profile_name, + expose_paths=expose_paths, + ) + ) + @server.tool() def get_file_asset_metadata(file_id: str) -> dict[str, Any]: """Return safe metadata and the resource URI for one indexed source file.""" @@ -176,6 +202,22 @@ def create_server( ) return resource["bytes"] + @server.prompt( + "how_to_use_wiki_file_assets", + description="Explain how to search and retrieve original source files from the wiki-file-assets MCP server.", + ) + def how_to_use_wiki_file_assets() -> str: + return ( + "wiki-file-assets serves original source files archived from an Obsidian wiki vault. " + "Use search_file_assets for keyword searches such as product codes, drawing names, sizes, " + "or document types. Use list_file_assets to browse allowed engineering files with optional " + "filters. Use get_file_asset_metadata with a file_id from search/list results to get the " + "asset://source/sha256/... URI, then read that resource to fetch the original file bytes. " + "This MCP is intentionally limited to safe engineering files such as drawings, CAD/3D models, " + "specifications, product parameters, and test reports; it blocks raw orders, prices, email " + "correspondence, complaint evidence, claims, and personal data." + ) + return server diff --git a/tests/test_mcp_server_remote.py b/tests/test_mcp_server_remote.py index 3e44363..6b59954 100644 --- a/tests/test_mcp_server_remote.py +++ b/tests/test_mcp_server_remote.py @@ -100,12 +100,15 @@ class RemoteMcpServerTests(unittest.TestCase): def test_create_server_uses_file_asset_service_name(self): class FakeMCP: names = [] + tool_names = [] + prompt_names = [] def __init__(self, name, **kwargs): self.names.append(name) def tool(self): def decorator(func): + self.tool_names.append(func.__name__) return func return decorator @@ -116,6 +119,13 @@ class RemoteMcpServerTests(unittest.TestCase): 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})()}), @@ -124,6 +134,10 @@ class RemoteMcpServerTests(unittest.TestCase): self.assertIsInstance(server, FakeMCP) self.assertEqual(FakeMCP.names[-1], "wiki-file-assets") + self.assertIn("search_file_assets", FakeMCP.tool_names) + self.assertIn("list_file_assets", FakeMCP.tool_names) + self.assertIn("get_file_asset_metadata", FakeMCP.tool_names) + self.assertIn("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: