feat(mcp): ingest direct text notes
This commit is contained in:
@@ -8,7 +8,7 @@ from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from obsidian_wiki import mcp_server
|
||||
from obsidian_wiki.mcp_ingest import IngestManager
|
||||
from obsidian_wiki.mcp_ingest import IngestError, IngestManager
|
||||
|
||||
|
||||
class FakeApp:
|
||||
@@ -356,6 +356,7 @@ class RemoteMcpServerTests(unittest.TestCase):
|
||||
self.assertIn("check_ingest_opencode_readiness", FakeMCP.tool_names)
|
||||
self.assertIn("get_ingest_upload_instructions", FakeMCP.tool_names)
|
||||
self.assertIn("create_ingest_upload_ticket", FakeMCP.tool_names)
|
||||
self.assertIn("ingest_text_note", FakeMCP.tool_names)
|
||||
self.assertNotIn("ingest_file_base64", FakeMCP.tool_names)
|
||||
|
||||
def test_create_server_registers_query_tools_only_when_enabled(self):
|
||||
@@ -536,12 +537,129 @@ class RemoteMcpServerTests(unittest.TestCase):
|
||||
doc_type_hint="drawing",
|
||||
start_job=False,
|
||||
)
|
||||
instructions = FakeMCP.tools["get_ingest_upload_instructions"]()
|
||||
|
||||
self.assertIn("upload_token", ticket)
|
||||
self.assertEqual(ticket["token_header"], "X-Obsidian-Wiki-Upload-Token")
|
||||
self.assertEqual(ticket["upload_path"], "/mcp-ingest/uploads")
|
||||
self.assertEqual(ticket["max_files"], 1)
|
||||
self.assertFalse(ticket["start_job"])
|
||||
self.assertIn("ingest_text_note", instructions["preferred_method"])
|
||||
self.assertEqual(instructions["direct_text_tool"]["name"], "ingest_text_note")
|
||||
|
||||
def test_ingest_text_note_tool_creates_completed_markdown_upload(self):
|
||||
class FakeMCP:
|
||||
tools = {}
|
||||
|
||||
def __init__(self, _name, **_kwargs):
|
||||
pass
|
||||
|
||||
def tool(self):
|
||||
def decorator(func):
|
||||
self.tools[func.__name__] = func
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
def resource(self, *_args, **_kwargs):
|
||||
def decorator(func):
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
def prompt(self, *_args, **_kwargs):
|
||||
def decorator(func):
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
with (
|
||||
tempfile.TemporaryDirectory() as tmp,
|
||||
patch.dict("sys.modules", {"mcp.server.fastmcp": type("Mod", (), {"FastMCP": FakeMCP})()}),
|
||||
):
|
||||
root = Path(tmp)
|
||||
manager = IngestManager(
|
||||
vault_path=root / "vault",
|
||||
project_root=root / "project",
|
||||
runtime_root=root / "runtime",
|
||||
max_file_bytes=200,
|
||||
)
|
||||
mcp_server.create_server(
|
||||
vault_path=root / "vault",
|
||||
enable_ingest=True,
|
||||
project_root=root / "project",
|
||||
ingest_runtime_root=root / "runtime",
|
||||
ingest_manager=manager,
|
||||
)
|
||||
result = FakeMCP.tools["ingest_text_note"](
|
||||
text="Remote agent thought\n\nNeed to remember this.",
|
||||
title="Customer thought: ACME / risk",
|
||||
project="bjd",
|
||||
doc_type_hint="thought",
|
||||
note="captured from remote agent",
|
||||
start_job=False,
|
||||
)
|
||||
payload = Path(result["uploads"][0]["path"]).read_text(encoding="utf-8")
|
||||
|
||||
self.assertEqual(result["job"], None)
|
||||
self.assertEqual(len(result["uploads"]), 1)
|
||||
upload = result["uploads"][0]
|
||||
self.assertEqual(upload["state"], "complete")
|
||||
self.assertEqual(upload["project"], "bjd")
|
||||
self.assertEqual(upload["doc_type_hint"], "thought")
|
||||
self.assertTrue(upload["file_name"].endswith(".md"))
|
||||
self.assertNotIn("/", upload["file_name"])
|
||||
self.assertIn("# Customer thought: ACME / risk", payload)
|
||||
self.assertIn("Remote agent thought", payload)
|
||||
|
||||
def test_ingest_text_note_tool_rejects_empty_and_oversized_text(self):
|
||||
class FakeMCP:
|
||||
tools = {}
|
||||
|
||||
def __init__(self, _name, **_kwargs):
|
||||
pass
|
||||
|
||||
def tool(self):
|
||||
def decorator(func):
|
||||
self.tools[func.__name__] = func
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
def resource(self, *_args, **_kwargs):
|
||||
def decorator(func):
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
def prompt(self, *_args, **_kwargs):
|
||||
def decorator(func):
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
with (
|
||||
tempfile.TemporaryDirectory() as tmp,
|
||||
patch.dict("sys.modules", {"mcp.server.fastmcp": type("Mod", (), {"FastMCP": FakeMCP})()}),
|
||||
):
|
||||
root = Path(tmp)
|
||||
manager = IngestManager(
|
||||
vault_path=root / "vault",
|
||||
project_root=root / "project",
|
||||
runtime_root=root / "runtime",
|
||||
max_file_bytes=20,
|
||||
)
|
||||
mcp_server.create_server(
|
||||
vault_path=root / "vault",
|
||||
enable_ingest=True,
|
||||
project_root=root / "project",
|
||||
ingest_runtime_root=root / "runtime",
|
||||
ingest_manager=manager,
|
||||
)
|
||||
with self.assertRaises(IngestError):
|
||||
FakeMCP.tools["ingest_text_note"](text=" ", start_job=False)
|
||||
with self.assertRaises(IngestError):
|
||||
FakeMCP.tools["ingest_text_note"](text="x" * 21, start_job=False)
|
||||
|
||||
def test_create_server_can_disable_file_asset_tools(self):
|
||||
class FakeMCP:
|
||||
|
||||
Reference in New Issue
Block a user