Files
GEWU/scripts/export_agent_contract.py
T

116 lines
4.5 KiB
Python
Raw Normal View History

"""Bind the GEWU agent pack to the MCP tools actually exposed by the service."""
from __future__ import annotations
import asyncio
import hashlib
import json
import shutil
import tempfile
from pathlib import Path
from typing import Any
from gewu.adapters.git_repository import GitCanonicalRepository
from gewu.adapters.sqlite_identity import SQLiteIdentityStore
from gewu.adapters.sqlite_search import SQLiteSearchIndex
from gewu.adapters.text_extractor import TextMarkdownExtractor
from gewu.application.acceptance import AcceptanceService
from gewu.application.context import ValidationContext
from gewu.application.reading import CanonicalNodeReader, CanonicalSourceReader
from gewu.application.search import CanonicalSearchService
from gewu.application.uploads import LocalArtifactStore
from gewu.domain.operations import Transport, operations_for
from gewu.transports.mcp_server import create_mcp_server
ROOT = Path(__file__).resolve().parents[1]
PACK = ROOT / "agents" / "gewu"
CONTRACT_PATH = PACK / "contracts" / "service-v1.json"
SCHEMA_OUTPUT = PACK / "contracts" / "schemas"
CATALOG_OUTPUT = PACK / "contracts" / "catalog-v1.json"
def _sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
async def _tool_inventory(data_root: Path) -> list[dict[str, Any]]:
repository = GitCanonicalRepository(data_root / "knowledge.git")
identities = SQLiteIdentityStore(data_root / "identity.sqlite3")
artifact_store = LocalArtifactStore(data_root / "artifacts")
def context_provider() -> ValidationContext:
return ValidationContext({}, {}, {}, {}, {})
acceptance_service = AcceptanceService(
repository=repository,
artifact_store=artifact_store,
workflow_root=data_root / "workflow",
context_provider=context_provider,
actor_is_current=lambda _actor: True,
)
search_service = CanonicalSearchService(
index=SQLiteSearchIndex(data_root / "index.sqlite3", repository),
project_ids_provider=lambda: (),
canonical_head=repository.head,
)
server = create_mcp_server(
identity_provider=identities,
node_reader=CanonicalNodeReader(repository),
source_reader=CanonicalSourceReader(repository),
search_service=search_service,
artifact_store=artifact_store,
extractor=TextMarkdownExtractor(),
acceptance_service=acceptance_service,
context_provider=context_provider,
)
tools = await server.list_tools()
return [
{
"name": tool.name,
"description": tool.description,
"input_schema": tool.input_schema,
}
for tool in tools
]
def build_contract(data_root: Path, *, schema_output: Path | None = None) -> dict[str, Any]:
catalog_path = ROOT / "schema" / "v1" / "catalog.json"
catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
tools = asyncio.run(_tool_inventory(data_root))
expected_names = {item.name for item in operations_for(Transport.MCP)}
actual_names = {tool["name"] for tool in tools}
if actual_names != expected_names:
raise RuntimeError("MCP tool inventory does not match the explicit operation catalog")
if schema_output is not None:
schema_output.mkdir(parents=True, exist_ok=True)
record_schemas = []
for source in sorted((ROOT / "schema" / "v1" / "records").glob("*.schema.json")):
destination = schema_output / source.name if schema_output is not None else source
if schema_output is not None:
shutil.copyfile(source, destination)
record_schemas.append({"path": f"schemas/{source.name}", "sha256": _sha256(destination)})
return {
"contract_id": "gewu.service.v1",
"contract_version": 1,
"api_version": catalog["api_version"],
"catalog_sha256": _sha256(catalog_path),
"operations": [item for item in catalog["operations"] if "mcp" in item["transports"]],
"record_schemas": record_schemas,
"tools": tools,
}
def main() -> None:
CONTRACT_PATH.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(ROOT / "schema" / "v1" / "catalog.json", CATALOG_OUTPUT)
with tempfile.TemporaryDirectory(prefix="gewu-agent-contract-") as temporary:
contract = build_contract(Path(temporary), schema_output=SCHEMA_OUTPUT)
CONTRACT_PATH.write_text(
json.dumps(contract, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
if __name__ == "__main__":
main()