1
0
Fork 0
Code Issues Pull requests Projects Releases 2 Packages Wiki Activity Actions Pages

feat: add adapter read-only MCP boundary

This commit is contained in:
Andraxion 2026-07-22 05:59:20 -04:00
parent 1943c36ef3
commit f1e31487c0
17 changed files with 209 additions and 45 deletions

View file

@ -5,6 +5,8 @@ import unittest
from dataclasses import replace
from pathlib import Path
from mcp.shared.memory import create_connected_server_and_client_session
from docforge.adapter_contract import (
AdapterEdge,
AdapterNode,
@ -16,6 +18,7 @@ from docforge.adapter_contract import (
)
from docforge.errors import DocForgeError
from docforge.index import ProjectIndex
from docforge.mcp_server import READ_TOOLS, create_read_only_server
from docforge.models import Edge, Node
@ -162,5 +165,62 @@ class AdapterContractTests(unittest.TestCase):
self.assertEqual(["timeline"], mismatch["changed"])
class AdapterReadOnlyMcpTests(unittest.IsolatedAsyncioTestCase):
async def test_adapter_project_exposes_only_read_tools_and_custom_context(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
fixture = AdapterContractTests()
project = AdapterProject(
Loader(fixture.projection(root)),
cache_root=root / ".cache" / "adapter-read-only",
)
index = ProjectIndex(project)
index.build()
calls: list[tuple[str, int | None]] = []
def context_provider(
current: ProjectIndex, profile: str, budget: int | None
) -> dict[str, object]:
checked = current.check()
calls.append((profile, budget))
return {
"status": "ok",
"project_id": checked["project_id"],
"project_root_fingerprint": checked["project_root_fingerprint"],
"revision": checked["revision"],
"source_hash": checked["source_hash"],
"adapter": checked["adapter"],
"profile": profile,
"budget": budget,
"estimated_tokens": 1,
"entries": [],
"omissions": [],
}
server = create_read_only_server(project, context_provider=context_provider)
async with create_connected_server_and_client_session(
server, raise_exceptions=True
) as session:
tools = await session.list_tools()
info = await session.call_tool("docforge_project_info", {})
contract = await session.call_tool("docforge_get_contract", {})
context = await session.call_tool(
"docforge_get_context", {"profile": "fixture", "budget": 321}
)
self.assertEqual(READ_TOOLS, tuple(tool.name for tool in tools.tools))
self.assertEqual("adapter-fixture", info.structuredContent["project_id"])
self.assertEqual(list(READ_TOOLS), contract.structuredContent["allowed_tools"])
self.assertIn(
"isolated_changeset_writes",
contract.structuredContent["excluded_operations"],
)
self.assertFalse(contract.structuredContent["proposal_access"]["enabled"])
self.assertFalse(contract.structuredContent["isolated_changeset_writes_allowed"])
self.assertEqual("fixture", context.structuredContent["profile"])
self.assertEqual([("fixture", 321)], calls)
self.assertFalse(project.descriptor.changeset_root.exists())
if __name__ == "__main__":
unittest.main()