and thats a server we can use
This commit is contained in:
@@ -3,12 +3,16 @@ name = "lda-wf"
|
|||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
description = "Add your description here"
|
description = "Add your description here"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
authors = [{ name = "lda", email = "[email protected]" }]
|
||||||
requires-python = ">=3.14"
|
requires-python = ">=3.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"mcp[cli,rich]>=1",
|
"mcp[cli,rich]>=1",
|
||||||
"pydantic>=2",
|
"pydantic>=2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
wf-mcp = "wf_mcp.cli:main"
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"pytest>=8",
|
"pytest>=8",
|
||||||
@@ -16,3 +20,6 @@ dev = [
|
|||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
addopts = "-p no:cacheprovider"
|
addopts = "-p no:cacheprovider"
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
package = true
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
def main() -> None:
|
||||||
|
print("Hello from lda-workflow-as-struct!")
|
||||||
@@ -2,6 +2,11 @@ from .adapters import (
|
|||||||
BackendAdapter,
|
BackendAdapter,
|
||||||
ToolCallResult,
|
ToolCallResult,
|
||||||
)
|
)
|
||||||
|
from .broker_server import (
|
||||||
|
build_service_from_config,
|
||||||
|
create_broker_server,
|
||||||
|
load_broker_config,
|
||||||
|
)
|
||||||
from .capabilities import (
|
from .capabilities import (
|
||||||
CatalogNodeEntry,
|
CatalogNodeEntry,
|
||||||
CatalogPromptEntry,
|
CatalogPromptEntry,
|
||||||
@@ -20,6 +25,7 @@ from .discovery import (
|
|||||||
from .events import McpEvent, make_event
|
from .events import McpEvent, make_event
|
||||||
from .models import (
|
from .models import (
|
||||||
AuthRecord,
|
AuthRecord,
|
||||||
|
BrokerConfig,
|
||||||
CatalogSnapshot,
|
CatalogSnapshot,
|
||||||
ConnectionConfig,
|
ConnectionConfig,
|
||||||
RawWorkflowPlan,
|
RawWorkflowPlan,
|
||||||
@@ -32,6 +38,7 @@ from .wrappers import wrap_discovered_tool
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
"AuthRecord",
|
"AuthRecord",
|
||||||
"BackendAdapter",
|
"BackendAdapter",
|
||||||
|
"BrokerConfig",
|
||||||
"CatalogNodeEntry",
|
"CatalogNodeEntry",
|
||||||
"CatalogPromptEntry",
|
"CatalogPromptEntry",
|
||||||
"CatalogResourceEntry",
|
"CatalogResourceEntry",
|
||||||
@@ -50,7 +57,10 @@ __all__ = [
|
|||||||
"Store",
|
"Store",
|
||||||
"ToolCallResult",
|
"ToolCallResult",
|
||||||
"WfMcpService",
|
"WfMcpService",
|
||||||
|
"build_service_from_config",
|
||||||
|
"create_broker_server",
|
||||||
"discover_connection_capabilities",
|
"discover_connection_capabilities",
|
||||||
|
"load_broker_config",
|
||||||
"make_event",
|
"make_event",
|
||||||
"parse_connection_id",
|
"parse_connection_id",
|
||||||
"qualify_node_name",
|
"qualify_node_name",
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from dataclasses import asdict
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Literal, cast
|
||||||
|
|
||||||
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
|
||||||
|
from .mcp_sdk_adapter import McpSdkAdapter
|
||||||
|
from .models import BrokerConfig, ConnectionConfig
|
||||||
|
from .service import WfMcpService
|
||||||
|
from .store import FileStore
|
||||||
|
|
||||||
|
|
||||||
|
def load_broker_config(path: str | Path) -> BrokerConfig:
|
||||||
|
config_path = Path(path)
|
||||||
|
data = json.loads(config_path.read_text(encoding="utf-8"))
|
||||||
|
store_root_raw = data.get("store_root", ".wf_mcp_store")
|
||||||
|
store_root = Path(store_root_raw)
|
||||||
|
if not store_root.is_absolute():
|
||||||
|
store_root = (config_path.parent / store_root).resolve()
|
||||||
|
|
||||||
|
connections = [ConnectionConfig(**item) for item in data.get("connections", [])]
|
||||||
|
return BrokerConfig(store_root=store_root, connections=connections)
|
||||||
|
|
||||||
|
|
||||||
|
def build_service_from_config(config: BrokerConfig) -> WfMcpService:
|
||||||
|
service = WfMcpService(store=FileStore(config.store_root))
|
||||||
|
for connection in config.connections:
|
||||||
|
service.register_connection(connection)
|
||||||
|
if connection.server not in service.adapters:
|
||||||
|
service.register_adapter(connection.server, McpSdkAdapter())
|
||||||
|
return service
|
||||||
|
|
||||||
|
|
||||||
|
def create_broker_server(service: WfMcpService) -> FastMCP:
|
||||||
|
server = FastMCP(
|
||||||
|
"wf-mcp-broker",
|
||||||
|
instructions=(
|
||||||
|
"A broker MCP server over one or more upstream MCP connections. "
|
||||||
|
"Use tools for refresh and invocation, resources for snapshots, "
|
||||||
|
"and prompts for planning against available capabilities."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@server.tool()
|
||||||
|
async def list_connections() -> list[dict[str, Any]]:
|
||||||
|
return [
|
||||||
|
asdict(connection)
|
||||||
|
for connection in sorted(
|
||||||
|
service.connections.list_all(),
|
||||||
|
key=lambda connection: connection.id,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
@server.tool()
|
||||||
|
async def refresh_connection_catalog(connection_id: str) -> dict[str, Any]:
|
||||||
|
await service.refresh_connection_catalog(connection_id)
|
||||||
|
snapshot = service.get_connection_snapshot(connection_id)
|
||||||
|
if snapshot is None:
|
||||||
|
return {"connection_id": connection_id, "refreshed": False}
|
||||||
|
return {
|
||||||
|
"connection_id": connection_id,
|
||||||
|
"refreshed": True,
|
||||||
|
"node_count": len(snapshot.nodes),
|
||||||
|
"resource_count": len(snapshot.resources),
|
||||||
|
"prompt_count": len(snapshot.prompts),
|
||||||
|
}
|
||||||
|
|
||||||
|
@server.tool()
|
||||||
|
async def get_catalog() -> dict[str, Any]:
|
||||||
|
return service.get_catalog().as_payload()
|
||||||
|
|
||||||
|
@server.tool()
|
||||||
|
async def read_broker_resource(qualified_name: str) -> dict[str, Any]:
|
||||||
|
return await service.read_resource(qualified_name)
|
||||||
|
|
||||||
|
@server.tool()
|
||||||
|
async def render_broker_prompt(
|
||||||
|
qualified_name: str,
|
||||||
|
arguments: dict[str, str] | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
return await service.render_prompt(qualified_name, arguments=arguments)
|
||||||
|
|
||||||
|
@server.tool()
|
||||||
|
async def invoke_broker_method(
|
||||||
|
connection_id: str,
|
||||||
|
method: str,
|
||||||
|
params: dict[str, Any] | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
return await service.invoke_method(connection_id, method, params=params)
|
||||||
|
|
||||||
|
@server.tool()
|
||||||
|
async def get_broker_events() -> list[dict[str, Any]]:
|
||||||
|
return [asdict(event) for event in service.list_events()]
|
||||||
|
|
||||||
|
@server.resource("wf-mcp://catalog", name="catalog.all")
|
||||||
|
def catalog_resource() -> str:
|
||||||
|
return json.dumps(service.get_catalog().as_payload(), indent=2)
|
||||||
|
|
||||||
|
@server.resource(
|
||||||
|
"wf-mcp://connection/{connection_id}/catalog",
|
||||||
|
name="catalog.connection",
|
||||||
|
)
|
||||||
|
def connection_catalog_resource(connection_id: str) -> str:
|
||||||
|
snapshot = service.get_connection_snapshot(connection_id)
|
||||||
|
if snapshot is None:
|
||||||
|
raise KeyError(connection_id)
|
||||||
|
return json.dumps(
|
||||||
|
{
|
||||||
|
"connection_id": snapshot.connection_id,
|
||||||
|
"fetched_at_epoch_ms": snapshot.fetched_at_epoch_ms,
|
||||||
|
"max_age_seconds": snapshot.max_age_seconds,
|
||||||
|
"nodes": [asdict(node) for node in snapshot.nodes],
|
||||||
|
"resources": [asdict(resource) for resource in snapshot.resources],
|
||||||
|
"prompts": [asdict(prompt) for prompt in snapshot.prompts],
|
||||||
|
"metadata": snapshot.metadata,
|
||||||
|
},
|
||||||
|
indent=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
@server.resource("wf-mcp://events", name="events.all")
|
||||||
|
def events_resource() -> str:
|
||||||
|
return json.dumps([asdict(event) for event in service.list_events()], indent=2)
|
||||||
|
|
||||||
|
@server.prompt(
|
||||||
|
name="plan_with_catalog",
|
||||||
|
description="Provide the broker catalog as planning context.",
|
||||||
|
)
|
||||||
|
def plan_with_catalog() -> list[dict[str, str]]:
|
||||||
|
payload = json.dumps(service.get_catalog().as_payload(), indent=2)
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": (
|
||||||
|
"Plan a workflow using this broker catalog. "
|
||||||
|
"Prefer existing namespaced capabilities.\n\n"
|
||||||
|
f"{payload}"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return server
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
config_path = os.environ.get("WF_MCP_CONFIG", "wf_mcp.config.json")
|
||||||
|
transport_env = os.environ.get("WF_MCP_TRANSPORT", "stdio")
|
||||||
|
run_broker_server(config_path, transport_env)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_transport(
|
||||||
|
transport: str,
|
||||||
|
) -> Literal["stdio", "sse", "streamable-http"]:
|
||||||
|
match transport:
|
||||||
|
case "streamable_http" | "streamable-http":
|
||||||
|
return "streamable-http"
|
||||||
|
case "stdio":
|
||||||
|
return "stdio"
|
||||||
|
case "sse":
|
||||||
|
return "sse"
|
||||||
|
case _:
|
||||||
|
raise ValueError(f"we dont support {transport} yet sry")
|
||||||
|
|
||||||
|
|
||||||
|
def run_broker_server(config_path: str | Path, transport: str = "stdio") -> None:
|
||||||
|
config = load_broker_config(config_path)
|
||||||
|
service = build_service_from_config(config)
|
||||||
|
server = create_broker_server(service)
|
||||||
|
server.run(transport=normalize_transport(transport))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from .broker_server import (
|
||||||
|
build_service_from_config,
|
||||||
|
load_broker_config,
|
||||||
|
run_broker_server,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_parser() -> argparse.ArgumentParser:
|
||||||
|
parser = argparse.ArgumentParser(prog="wf-mcp")
|
||||||
|
parser.add_argument(
|
||||||
|
"--config",
|
||||||
|
default="wf_mcp.config.json",
|
||||||
|
help="Path to broker config JSON.",
|
||||||
|
)
|
||||||
|
|
||||||
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
serve = subparsers.add_parser("serve", help="Run the broker MCP server.")
|
||||||
|
serve.add_argument(
|
||||||
|
"--transport",
|
||||||
|
default="stdio",
|
||||||
|
choices=["stdio", "sse", "streamable-http", "streamable_http"],
|
||||||
|
help="Transport to run the broker server with.",
|
||||||
|
)
|
||||||
|
|
||||||
|
subparsers.add_parser("connections", help="List configured connections.")
|
||||||
|
subparsers.add_parser("catalog", help="Print the broker catalog as JSON.")
|
||||||
|
|
||||||
|
refresh = subparsers.add_parser(
|
||||||
|
"refresh",
|
||||||
|
help="Refresh one connection catalog or all configured connections.",
|
||||||
|
)
|
||||||
|
refresh.add_argument("connection_id", nargs="?", help="Connection id to refresh.")
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def _service_from_config(config_path: str | Path):
|
||||||
|
config = load_broker_config(config_path)
|
||||||
|
return build_service_from_config(config)
|
||||||
|
|
||||||
|
|
||||||
|
def _json_dump(data: Any) -> None:
|
||||||
|
print(json.dumps(data, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
parser = build_parser()
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
if args.command == "serve":
|
||||||
|
run_broker_server(args.config, args.transport)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
service = _service_from_config(args.config)
|
||||||
|
|
||||||
|
if args.command == "connections":
|
||||||
|
_json_dump(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": connection.id,
|
||||||
|
"server": connection.server,
|
||||||
|
"account": connection.account,
|
||||||
|
"enabled": connection.enabled,
|
||||||
|
"metadata": connection.metadata,
|
||||||
|
}
|
||||||
|
for connection in service.connections.list_all()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if args.command == "catalog":
|
||||||
|
_json_dump(service.get_catalog().as_payload())
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if args.command == "refresh":
|
||||||
|
if args.connection_id:
|
||||||
|
asyncio.run(service.refresh_connection_catalog(args.connection_id))
|
||||||
|
else:
|
||||||
|
for connection in service.connections.list_enabled():
|
||||||
|
asyncio.run(service.refresh_connection_catalog(connection.id))
|
||||||
|
_json_dump(service.get_catalog().as_payload())
|
||||||
|
return 0
|
||||||
|
|
||||||
|
parser.error(f"unknown command {args.command!r}")
|
||||||
|
return 2
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -32,6 +32,9 @@ class ConnectionRegistry:
|
|||||||
def get(self, connection_id: str) -> ConnectionConfig:
|
def get(self, connection_id: str) -> ConnectionConfig:
|
||||||
return self.connections[connection_id]
|
return self.connections[connection_id]
|
||||||
|
|
||||||
|
def list_all(self) -> list[ConnectionConfig]:
|
||||||
|
return list(self.connections.values())
|
||||||
|
|
||||||
def list_enabled(self) -> list[ConnectionConfig]:
|
def list_enabled(self) -> list[ConnectionConfig]:
|
||||||
return [
|
return [
|
||||||
connection for connection in self.connections.values() if connection.enabled
|
connection for connection in self.connections.values() if connection.enabled
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import asdict, dataclass, field
|
from dataclasses import asdict, dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from .capabilities import CatalogNodeEntry, CatalogPromptEntry, CatalogResourceEntry
|
from .capabilities import CatalogNodeEntry, CatalogPromptEntry, CatalogResourceEntry
|
||||||
@@ -48,6 +49,12 @@ class RawWorkflowPlan:
|
|||||||
edges: list[dict[str, Any]]
|
edges: list[dict[str, Any]]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(slots=True)
|
||||||
|
class BrokerConfig:
|
||||||
|
store_root: Path
|
||||||
|
connections: list[ConnectionConfig] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
def dump_catalog_snapshot(snapshot: CatalogSnapshot) -> dict[str, Any]:
|
def dump_catalog_snapshot(snapshot: CatalogSnapshot) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"connection_id": snapshot.connection_id,
|
"connection_id": snapshot.connection_id,
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
|
||||||
if str(ROOT) not in sys.path:
|
|
||||||
sys.path.insert(0, str(ROOT))
|
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
|
||||||
|
from wf_mcp import (
|
||||||
|
BrokerConfig,
|
||||||
|
ConnectionConfig,
|
||||||
|
FileStore,
|
||||||
|
WfMcpService,
|
||||||
|
build_service_from_config,
|
||||||
|
create_broker_server,
|
||||||
|
load_broker_config,
|
||||||
|
)
|
||||||
|
|
||||||
|
from test_wf_mcp_support import FakeAdapter, local_temp_root
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_broker_config_resolves_relative_store_root() -> None:
|
||||||
|
tmp_path = local_temp_root() / "broker_config_test"
|
||||||
|
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
config_path = tmp_path / "wf_mcp.config.json"
|
||||||
|
config_path.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"store_root": ".broker-store",
|
||||||
|
"connections": [
|
||||||
|
{
|
||||||
|
"id": "demo.personal",
|
||||||
|
"server": "demo",
|
||||||
|
"account": "personal",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
config = load_broker_config(config_path)
|
||||||
|
|
||||||
|
assert config.store_root == (tmp_path / ".broker-store").resolve()
|
||||||
|
assert [connection.id for connection in config.connections] == ["demo.personal"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_broker_server_exposes_tools_resources_and_prompts() -> None:
|
||||||
|
service = WfMcpService(store=FileStore(local_temp_root() / "broker_server_store"))
|
||||||
|
service.register_connection(
|
||||||
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
|
)
|
||||||
|
service.register_adapter("demo", FakeAdapter())
|
||||||
|
asyncio.run(service.refresh_connection_catalog("demo.personal"))
|
||||||
|
|
||||||
|
server = create_broker_server(service)
|
||||||
|
|
||||||
|
tools = asyncio.run(server.list_tools())
|
||||||
|
resources = asyncio.run(server.list_resources())
|
||||||
|
prompts = asyncio.run(server.list_prompts())
|
||||||
|
|
||||||
|
tool_names = {tool.name for tool in tools}
|
||||||
|
resource_names = {resource.name for resource in resources}
|
||||||
|
prompt_names = {prompt.name for prompt in prompts}
|
||||||
|
|
||||||
|
assert "refresh_connection_catalog" in tool_names
|
||||||
|
assert "invoke_broker_method" in tool_names
|
||||||
|
assert "catalog.all" in resource_names
|
||||||
|
assert "events.all" in resource_names
|
||||||
|
assert "plan_with_catalog" in prompt_names
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_service_from_config_registers_connections() -> None:
|
||||||
|
config = BrokerConfig(
|
||||||
|
store_root=local_temp_root() / "broker_config_store",
|
||||||
|
connections=[
|
||||||
|
ConnectionConfig(id="demo.personal", server="demo", account="personal"),
|
||||||
|
ConnectionConfig(id="demo.work", server="demo", account="work"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
service = build_service_from_config(config)
|
||||||
|
|
||||||
|
ids = [connection.id for connection in service.connections.list_all()]
|
||||||
|
assert ids == ["demo.personal", "demo.work"]
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from wf_mcp.cli import build_parser, main
|
||||||
|
|
||||||
|
from test_wf_mcp_support import local_temp_root
|
||||||
|
|
||||||
|
|
||||||
|
def _write_config(path: Path) -> None:
|
||||||
|
path.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"store_root": ".wf_mcp_store",
|
||||||
|
"connections": [
|
||||||
|
{
|
||||||
|
"id": "demo.personal",
|
||||||
|
"server": "demo",
|
||||||
|
"account": "personal",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_parser_accepts_serve_transport() -> None:
|
||||||
|
parser = build_parser()
|
||||||
|
args = parser.parse_args(
|
||||||
|
["--config", "wf_mcp.config.json", "serve", "--transport", "streamable_http"]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert args.command == "serve"
|
||||||
|
assert args.transport == "streamable_http"
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_connections_prints_configured_connections(capsys) -> None:
|
||||||
|
tmp_path = local_temp_root() / "cli_connections_test"
|
||||||
|
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
config_path = tmp_path / "wf_mcp.config.json"
|
||||||
|
_write_config(config_path)
|
||||||
|
|
||||||
|
exit_code = main(["--config", str(config_path), "connections"])
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
|
||||||
|
assert exit_code == 0
|
||||||
|
payload = json.loads(captured.out)
|
||||||
|
assert payload[0]["id"] == "demo.personal"
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_catalog_prints_empty_catalog_when_not_refreshed(
|
||||||
|
capsys,
|
||||||
|
) -> None:
|
||||||
|
tmp_path = local_temp_root() / "cli_catalog_test"
|
||||||
|
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
config_path = tmp_path / "wf_mcp.config.json"
|
||||||
|
_write_config(config_path)
|
||||||
|
|
||||||
|
exit_code = main(["--config", str(config_path), "catalog"])
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
|
||||||
|
assert exit_code == 0
|
||||||
|
payload = json.loads(captured.out)
|
||||||
|
assert payload["nodes"] == []
|
||||||
|
assert payload["resources"] == []
|
||||||
|
assert payload["prompts"] == []
|
||||||
@@ -251,7 +251,7 @@ wheels = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "lda-wf"
|
name = "lda-wf"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
source = { virtual = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "mcp", extra = ["cli", "rich"] },
|
{ name = "mcp", extra = ["cli", "rich"] },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"store_root": ".wf_mcp_store",
|
||||||
|
"connections": [
|
||||||
|
{
|
||||||
|
"id": "everything.default",
|
||||||
|
"server": "everything",
|
||||||
|
"account": "default",
|
||||||
|
"enabled": true,
|
||||||
|
"metadata": {
|
||||||
|
"transport": "stdio",
|
||||||
|
"command": "pnpx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-everything"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"store_root": ".wf_mcp_store",
|
||||||
|
"connections": [
|
||||||
|
{
|
||||||
|
"id": "everything.default",
|
||||||
|
"server": "everything",
|
||||||
|
"account": "default",
|
||||||
|
"enabled": true,
|
||||||
|
"metadata": {
|
||||||
|
"transport": "stdio",
|
||||||
|
"command": "pnpx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-everything"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user