diff --git a/src/nodered_mcp/tools/flows.py b/src/nodered_mcp/tools/flows.py index 1d86ead..4f864d8 100644 --- a/src/nodered_mcp/tools/flows.py +++ b/src/nodered_mcp/tools/flows.py @@ -75,7 +75,15 @@ async def update_flows(flows_json: str, deployment_type: str = "full") -> str: Returns: Confirmation message. + + Raises: + ValueError: If deployment_type is not one of: full, nodes, flows, reload. """ + _VALID_DEPLOYMENT_TYPES = ("full", "nodes", "flows", "reload") + if deployment_type not in _VALID_DEPLOYMENT_TYPES: + raise ValueError( + f"Invalid deployment_type '{deployment_type}'. Must be one of: {', '.join(_VALID_DEPLOYMENT_TYPES)}" + ) client = get_client() flows = json.loads(flows_json) await client.update_flows(flows, deployment_type=deployment_type) @@ -144,9 +152,15 @@ async def create_flow(flow_json: str) -> FlowCreateResult: Returns: FlowCreateResult with the new flow ID. + + Raises: + ValueError: If flow_json is not valid JSON. """ + try: + flow = json.loads(flow_json) + except json.JSONDecodeError as e: + raise ValueError(f"Invalid JSON in flow_json: {e}") from e client = get_client() - flow = json.loads(flow_json) result = await client.create_flow(flow) return FlowCreateResult.from_api(result) @@ -187,7 +201,12 @@ async def set_flows_state(state: str) -> FlowState: Returns: FlowState with new state. + + Raises: + ValueError: If state is not "start" or "stop". """ + if state not in ("start", "stop"): + raise ValueError(f"Invalid state '{state}'. Must be 'start' or 'stop'.") client = get_client() raw_state = await client.set_flows_state(state) return FlowState.from_api(raw_state) diff --git a/src/nodered_mcp/tools/utility.py b/src/nodered_mcp/tools/utility.py index 509994a..9846f99 100644 --- a/src/nodered_mcp/tools/utility.py +++ b/src/nodered_mcp/tools/utility.py @@ -2,6 +2,32 @@ from nodered_mcp.server import mcp +# Centralized endpoint registry: maps path to (method, description, implemented) +_ENDPOINT_REGISTRY = [ + ("GET", "/flows", "Get all flows", True), + ("GET", "/flows/formatted", "Get flows with formatted summary", True), + ("GET", "/flows/visualize", "Visualize flow structure", True), + ("POST", "/flows", "Update all flows", True), + ("GET", "/flow/:id", "Get a specific flow", True), + ("PUT", "/flow/:id", "Update a specific flow", True), + ("PATCH", "/flow/:id", "Patch a flow incrementally", True), + ("DELETE","/flow/:id", "Delete a specific flow", True), + ("POST", "/flow", "Create a new flow", True), + ("GET", "/flows/state", "Get the state of flows", True), + ("POST", "/flows/state", "Set the state of flows", True), + ("GET", "/tabs", "List all flow tabs", True), + ("GET", "/nodes", "Get list of installed nodes", True), + ("GET", "/nodes/:module", "Get node module info", True), + ("GET", "/nodes/search", "Search for nodes", True), + ("GET", "/nodes/find", "Find nodes by type", True), + ("POST", "/nodes/:module/toggle", "Toggle node module", True), + ("POST", "/nodes", "Install a new node module", False), + ("GET", "/settings", "Get runtime settings", True), + ("GET", "/diagnostics", "Get diagnostics information", True), + ("POST", "/inject/:id", "Trigger an inject node", True), + ("GET", "/lint/:id", "Lint flow layout", True), +] + @mcp.tool() async def api_help() -> str: @@ -9,23 +35,8 @@ async def api_help() -> str: Returns: Markdown table of API endpoints and their MCP implementation status. + Endpoints are defined in the centralized _ENDPOINT_REGISTRY. """ - endpoints = [ - ("GET", "/flows", "Get all flows", True), - ("POST", "/flows", "Update all flows", True), - ("GET", "/flow/:id", "Get a specific flow", True), - ("PUT", "/flow/:id", "Update a specific flow", True), - ("DELETE", "/flow/:id", "Delete a specific flow", True), - ("POST", "/flow", "Create a new flow", True), - ("GET", "/flows/state", "Get the state of flows", True), - ("POST", "/flows/state", "Set the state of flows", True), - ("GET", "/nodes", "Get list of installed nodes", True), - ("POST", "/nodes", "Install a new node module", False), - ("GET", "/settings", "Get runtime settings", True), - ("GET", "/diagnostics", "Get diagnostics information", True), - ("POST", "/inject/:id", "Trigger an inject node", True), - ] - output = [ "# Node-RED API Help", "", @@ -33,8 +44,8 @@ async def api_help() -> str: "|--------|------|-------------|---------------------|", ] - for method, path, description, implemented in endpoints: - status = "✅" if implemented else "❌" + for method, path, description, implemented in _ENDPOINT_REGISTRY: + status = "\u2705" if implemented else "\u274c" output.append(f"| {method} | {path} | {description} | {status} |") return "\n".join(output)