refactor: move mcp catalog dtos to wf_sources_mcp
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from wf_api.auth import AuthRecord as NeutralAuthRecord
|
||||
from wf_mcp.catalog.models import CatalogSnapshot
|
||||
from wf_sources_mcp.auth import (
|
||||
AuthRecord,
|
||||
mcp_auth_env,
|
||||
@@ -9,6 +8,7 @@ from wf_sources_mcp.auth import (
|
||||
mcp_auth_headers,
|
||||
neutral_auth_from_mcp,
|
||||
)
|
||||
from wf_sources_mcp.catalog import CatalogSnapshot
|
||||
from wf_sources_mcp.storage import FileAuthStore, FileCatalogStore, FileStore
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from wf_sources_mcp.catalog import (
|
||||
CatalogNodeEntry,
|
||||
CatalogPromptEntry,
|
||||
CatalogResourceEntry,
|
||||
CatalogSnapshot,
|
||||
DiscoveredPrompt,
|
||||
DiscoveredResource,
|
||||
DiscoveredTool,
|
||||
dump_catalog_snapshot,
|
||||
)
|
||||
|
||||
|
||||
def test_discovered_tool_default_outcome_and_metadata() -> None:
|
||||
tool = DiscoveredTool(
|
||||
name="echo",
|
||||
title=None,
|
||||
description="Echo input",
|
||||
input_schema={"type": "object"},
|
||||
output_schema={"type": "object"},
|
||||
)
|
||||
|
||||
assert tool.outcomes == ("ok",)
|
||||
assert tool.metadata == {}
|
||||
|
||||
|
||||
def test_discovered_resource_and_prompt_keep_structural_fields() -> None:
|
||||
resource = DiscoveredResource(
|
||||
uri="docs://guide",
|
||||
name="guide",
|
||||
title="Guide",
|
||||
description="Read me",
|
||||
mime_type="text/markdown",
|
||||
)
|
||||
prompt = DiscoveredPrompt(
|
||||
name="summarize",
|
||||
title=None,
|
||||
description="Summarize",
|
||||
arguments=[{"name": "topic"}],
|
||||
)
|
||||
|
||||
assert resource.uri == "docs://guide"
|
||||
assert resource.mime_type == "text/markdown"
|
||||
assert prompt.arguments == [{"name": "topic"}]
|
||||
|
||||
|
||||
def test_catalog_snapshot_staleness_and_dump_shape() -> None:
|
||||
snapshot = CatalogSnapshot(
|
||||
connection_id="demo.default",
|
||||
fetched_at_epoch_ms=1_000,
|
||||
max_age_seconds=2,
|
||||
nodes=[
|
||||
CatalogNodeEntry(
|
||||
qualified_name="demo.default.echo",
|
||||
connection_id="demo.default",
|
||||
local_name="echo",
|
||||
title=None,
|
||||
description="Echo",
|
||||
outcomes=("ok",),
|
||||
input_schema={"type": "object"},
|
||||
output_schema={"type": "object"},
|
||||
)
|
||||
],
|
||||
resources=[
|
||||
CatalogResourceEntry(
|
||||
qualified_name="demo.default.guide",
|
||||
connection_id="demo.default",
|
||||
local_name="guide",
|
||||
title=None,
|
||||
uri="docs://guide",
|
||||
description="Guide",
|
||||
)
|
||||
],
|
||||
prompts=[
|
||||
CatalogPromptEntry(
|
||||
qualified_name="demo.default.summarize",
|
||||
connection_id="demo.default",
|
||||
local_name="summarize",
|
||||
title=None,
|
||||
description="Summarize",
|
||||
)
|
||||
],
|
||||
metadata={"source": "test"},
|
||||
)
|
||||
|
||||
assert snapshot.is_stale(3_001) is True
|
||||
dumped = dump_catalog_snapshot(snapshot)
|
||||
assert dumped["connection_id"] == "demo.default"
|
||||
assert dumped["nodes"][0]["qualified_name"] == "demo.default.echo"
|
||||
assert dumped["resources"][0]["uri"] == "docs://guide"
|
||||
assert dumped["prompts"][0]["local_name"] == "summarize"
|
||||
assert dumped["metadata"] == {"source": "test"}
|
||||
@@ -4,8 +4,9 @@ import ast
|
||||
from pathlib import Path
|
||||
|
||||
# Temporary low-level wf_mcp imports are allowed for connection id parsing,
|
||||
# reserved names, and broker DTO conversion. Frontend/proxy/workflow-surface
|
||||
# imports are forbidden because wf_sources_mcp is upstream-source code.
|
||||
# reserved names, and broker DTO conversion. Catalog DTOs should now be local
|
||||
# to wf_sources_mcp. Frontend/proxy/workflow-surface imports are forbidden
|
||||
# because wf_sources_mcp is upstream-source code.
|
||||
FORBIDDEN_WF_MCP_PREFIXES = (
|
||||
"wf_mcp.admin_surface",
|
||||
"wf_mcp.workflow_surface",
|
||||
@@ -40,3 +41,34 @@ def test_wf_sources_mcp_does_not_import_frontend_mcp_modules() -> None:
|
||||
"wf_sources_mcp imports frontend/proxy MCP modules:\n"
|
||||
+ "\n".join(f" {violation}" for violation in violations)
|
||||
)
|
||||
|
||||
|
||||
def test_wf_sources_mcp_does_not_import_wf_mcp_catalog_dtos() -> None:
|
||||
root = Path(__file__).resolve().parents[2] / "src" / "wf_sources_mcp"
|
||||
forbidden = {
|
||||
"wf_mcp.capabilities",
|
||||
"wf_mcp.catalog",
|
||||
"wf_mcp.catalog.models",
|
||||
}
|
||||
violations: list[str] = []
|
||||
|
||||
for py_file in sorted(root.rglob("*.py")):
|
||||
rel = py_file.relative_to(root.parent)
|
||||
module = str(rel.with_suffix("")).replace("/", ".").replace("\\", ".")
|
||||
tree = ast.parse(py_file.read_text(encoding="utf-8"), filename=str(py_file))
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.ImportFrom) and node.module in forbidden:
|
||||
violations.append(
|
||||
f"{module}:{node.lineno}: from {node.module} import ..."
|
||||
)
|
||||
elif isinstance(node, ast.Import):
|
||||
for alias in node.names:
|
||||
if alias.name in forbidden:
|
||||
violations.append(
|
||||
f"{module}:{node.lineno}: import {alias.name}"
|
||||
)
|
||||
|
||||
assert violations == [], (
|
||||
"wf_sources_mcp still imports old wf_mcp catalog DTO modules:\n"
|
||||
+ "\n".join(f" {violation}" for violation in violations)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user