feat(mcp): ingest direct text notes

This commit is contained in:
Codex
2026-07-08 11:18:32 +00:00
parent 37e6ce5673
commit 688bebb64c
4 changed files with 182 additions and 5 deletions
+119 -1
View File
@@ -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: