181 lines
5.4 KiB
Python
181 lines
5.4 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import asyncio
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from .broker import (
|
|
build_service_from_config,
|
|
load_broker_config,
|
|
)
|
|
from .server import run_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 MCP server.")
|
|
serve.add_argument(
|
|
"--transport",
|
|
default="stdio",
|
|
choices=["stdio", "sse", "streamable-http", "streamable_http"],
|
|
help="Transport to run the MCP server with.",
|
|
)
|
|
serve.add_argument(
|
|
"--resources-as-tools",
|
|
action="store_true",
|
|
help="Expose proxied resources through list_resources/read_resource tools.",
|
|
)
|
|
serve.add_argument(
|
|
"--prompts-as-tools",
|
|
action="store_true",
|
|
help="Expose proxied prompts through list_prompts/get_prompt tools.",
|
|
)
|
|
serve.add_argument(
|
|
"--search-tools",
|
|
action="store_true",
|
|
help="Collapse a large tool catalog into a search interface, for discovery on demand",
|
|
)
|
|
serve.add_argument(
|
|
"--safe-tool-names",
|
|
action="store_true",
|
|
help=(
|
|
"Expose runtime tool names using only letters, numbers, underscore, "
|
|
"and dash for strict clients such as Claude Desktop MCPB."
|
|
),
|
|
)
|
|
serve.add_argument(
|
|
"--no-admin-tools",
|
|
dest="admin_tools",
|
|
action="store_false",
|
|
help="Hide wf.admin.* tools.",
|
|
)
|
|
serve.set_defaults(admin_tools=True)
|
|
|
|
subparsers.add_parser("connections", help="List configured connections.")
|
|
subparsers.add_parser("status", help="Show connection status and snapshot counts.")
|
|
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))
|
|
|
|
|
|
async def _refresh_all(service, connection_id: str | None) -> list[dict[str, Any]]:
|
|
target_ids = (
|
|
[connection_id]
|
|
if connection_id is not None
|
|
else [connection.id for connection in service.connections.list_enabled()]
|
|
)
|
|
results: list[dict[str, Any]] = []
|
|
for target_id in target_ids:
|
|
try:
|
|
await service.refresh_connection_catalog(target_id)
|
|
snapshot = service.get_connection_snapshot(target_id)
|
|
results.append(
|
|
{
|
|
"connection_id": target_id,
|
|
"refreshed": snapshot is not None,
|
|
"node_count": 0 if snapshot is None else len(snapshot.nodes),
|
|
"resource_count": 0
|
|
if snapshot is None
|
|
else len(snapshot.resources),
|
|
"prompt_count": 0 if snapshot is None else len(snapshot.prompts),
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
results.append(
|
|
{
|
|
"connection_id": target_id,
|
|
"refreshed": False,
|
|
"error_type": type(exc).__name__,
|
|
"error": str(exc),
|
|
}
|
|
)
|
|
return results
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = build_parser()
|
|
args = parser.parse_args(argv)
|
|
|
|
if args.command == "serve":
|
|
config = load_broker_config(args.config)
|
|
run_server(
|
|
config,
|
|
args.transport,
|
|
config_path=args.config,
|
|
resources_as_tools=args.resources_as_tools,
|
|
prompts_as_tools=args.prompts_as_tools,
|
|
search_tools=args.search_tools,
|
|
safe_tool_names=args.safe_tool_names,
|
|
admin_tools=args.admin_tools,
|
|
)
|
|
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 == "status":
|
|
_json_dump(service.connection_statuses())
|
|
return 0
|
|
|
|
if args.command == "catalog":
|
|
_json_dump(service.get_catalog().as_payload())
|
|
return 0
|
|
|
|
if args.command == "refresh":
|
|
results = asyncio.run(_refresh_all(service, args.connection_id))
|
|
_json_dump(
|
|
{
|
|
"results": results,
|
|
"catalog": service.get_catalog().as_payload(),
|
|
}
|
|
)
|
|
if any(not result["refreshed"] for result in results):
|
|
return 1
|
|
return 0
|
|
|
|
parser.error(f"unknown command {args.command!r}")
|
|
return 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|