Initial commit: MCP Hub Community Edition v3.0.0
Community edition generated from private repo via sync pipeline. Includes 9 plugins (WordPress, WooCommerce, WP Advanced, Gitea, n8n, Supabase, OpenPanel, Appwrite, Directus) with ~587 tools. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
23
plugins/n8n/handlers/__init__.py
Normal file
23
plugins/n8n/handlers/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""n8n Plugin Handlers"""
|
||||
|
||||
from plugins.n8n.handlers import (
|
||||
credentials,
|
||||
executions,
|
||||
projects,
|
||||
system,
|
||||
tags,
|
||||
users,
|
||||
variables,
|
||||
workflows,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"workflows",
|
||||
"executions",
|
||||
"credentials",
|
||||
"tags",
|
||||
"users",
|
||||
"projects",
|
||||
"variables",
|
||||
"system",
|
||||
]
|
||||
161
plugins/n8n/handlers/credentials.py
Normal file
161
plugins/n8n/handlers/credentials.py
Normal file
@@ -0,0 +1,161 @@
|
||||
"""Credentials Handler - manages n8n credentials"""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.n8n.client import N8nClient
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator"""
|
||||
return [
|
||||
{
|
||||
"name": "get_credential",
|
||||
"method_name": "get_credential",
|
||||
"description": "Get credential metadata by ID. Note: Listing all credentials is not supported in n8n Public API.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"credential_id": {"type": "string", "minLength": 1}},
|
||||
"required": ["credential_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_credential",
|
||||
"method_name": "create_credential",
|
||||
"description": "Create a new credential. Use get_credential_schema to see required fields.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string", "minLength": 1, "description": "Credential name"},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "Credential type (e.g., 'githubApi')",
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"description": "Credential data matching the type schema",
|
||||
},
|
||||
},
|
||||
"required": ["name", "type", "data"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "delete_credential",
|
||||
"method_name": "delete_credential",
|
||||
"description": "Delete a credential.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"credential_id": {"type": "string", "minLength": 1}},
|
||||
"required": ["credential_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "get_credential_schema",
|
||||
"method_name": "get_credential_schema",
|
||||
"description": "Get the schema for a credential type. Shows required fields.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"credential_type": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "Credential type name",
|
||||
}
|
||||
},
|
||||
"required": ["credential_type"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "transfer_credential",
|
||||
"method_name": "transfer_credential",
|
||||
"description": "[Enterprise] Transfer a credential to another project. Requires n8n Enterprise/Pro license.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"credential_id": {"type": "string", "minLength": 1},
|
||||
"destination_project_id": {"type": "string", "minLength": 1},
|
||||
},
|
||||
"required": ["credential_id", "destination_project_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
]
|
||||
|
||||
# === HANDLER FUNCTIONS ===
|
||||
# Note: list_credentials is not supported in n8n Public API (GET /credentials returns 405)
|
||||
|
||||
async def get_credential(client: N8nClient, credential_id: str) -> str:
|
||||
"""Get credential metadata"""
|
||||
try:
|
||||
cred = await client.get_credential(credential_id)
|
||||
result = {
|
||||
"success": True,
|
||||
"credential": {
|
||||
"id": cred.get("id"),
|
||||
"name": cred.get("name"),
|
||||
"type": cred.get("type"),
|
||||
"created_at": cred.get("createdAt"),
|
||||
"updated_at": cred.get("updatedAt"),
|
||||
},
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def create_credential(client: N8nClient, name: str, type: str, data: dict[str, Any]) -> str:
|
||||
"""Create a new credential"""
|
||||
try:
|
||||
cred_data = {"name": name, "type": type, "data": data}
|
||||
cred = await client.create_credential(cred_data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Credential '{name}' created successfully",
|
||||
"credential": {
|
||||
"id": cred.get("id"),
|
||||
"name": cred.get("name"),
|
||||
"type": cred.get("type"),
|
||||
},
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_credential(client: N8nClient, credential_id: str) -> str:
|
||||
"""Delete a credential"""
|
||||
try:
|
||||
await client.delete_credential(credential_id)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Credential {credential_id} deleted"}, indent=2
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def get_credential_schema(client: N8nClient, credential_type: str) -> str:
|
||||
"""Get credential type schema"""
|
||||
try:
|
||||
schema = await client.get_credential_schema(credential_type)
|
||||
return json.dumps(
|
||||
{"success": True, "credential_type": credential_type, "schema": schema}, indent=2
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def transfer_credential(
|
||||
client: N8nClient, credential_id: str, destination_project_id: str
|
||||
) -> str:
|
||||
"""Transfer credential to another project"""
|
||||
try:
|
||||
await client.transfer_credential(credential_id, destination_project_id)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Credential {credential_id} transferred to project {destination_project_id}",
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
437
plugins/n8n/handlers/executions.py
Normal file
437
plugins/n8n/handlers/executions.py
Normal file
@@ -0,0 +1,437 @@
|
||||
"""Execution Handler - manages n8n workflow executions"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.n8n.client import N8nClient
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator"""
|
||||
return [
|
||||
# === LIST EXECUTIONS ===
|
||||
{
|
||||
"name": "list_executions",
|
||||
"method_name": "list_executions",
|
||||
"description": "List workflow executions with filters by status, workflow, and date. Returns execution history. All filter parameters are OPTIONAL.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workflow_id": {
|
||||
"type": "string",
|
||||
"description": "OPTIONAL: Filter by workflow ID. Omit for all executions.",
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["success", "error", "waiting", "running", "new"],
|
||||
"description": "OPTIONAL: Filter by execution status. Omit for all statuses.",
|
||||
},
|
||||
"include_data": {
|
||||
"type": "boolean",
|
||||
"description": "Include full execution data",
|
||||
"default": False,
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Maximum results",
|
||||
"default": 20,
|
||||
"minimum": 1,
|
||||
"maximum": 250,
|
||||
},
|
||||
"cursor": {"type": "string", "description": "OPTIONAL: Pagination cursor."},
|
||||
},
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
# === GET EXECUTION ===
|
||||
{
|
||||
"name": "get_execution",
|
||||
"method_name": "get_execution",
|
||||
"description": "Get detailed information about a specific execution including status, timing, and node outputs.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"execution_id": {
|
||||
"type": "string",
|
||||
"description": "Execution ID",
|
||||
"minLength": 1,
|
||||
},
|
||||
"include_data": {
|
||||
"type": "boolean",
|
||||
"description": "Include full node execution data",
|
||||
"default": True,
|
||||
},
|
||||
},
|
||||
"required": ["execution_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
# === DELETE EXECUTION ===
|
||||
{
|
||||
"name": "delete_execution",
|
||||
"method_name": "delete_execution",
|
||||
"description": "Delete a single execution record. This removes the execution from history.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"execution_id": {
|
||||
"type": "string",
|
||||
"description": "Execution ID to delete",
|
||||
"minLength": 1,
|
||||
}
|
||||
},
|
||||
"required": ["execution_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === DELETE EXECUTIONS (BULK) ===
|
||||
{
|
||||
"name": "delete_executions",
|
||||
"method_name": "delete_executions",
|
||||
"description": "Bulk delete multiple execution records.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"execution_ids": {
|
||||
"type": "array",
|
||||
"description": "List of execution IDs to delete",
|
||||
"items": {"type": "string"},
|
||||
"minItems": 1,
|
||||
}
|
||||
},
|
||||
"required": ["execution_ids"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === STOP EXECUTION ===
|
||||
{
|
||||
"name": "stop_execution",
|
||||
"method_name": "stop_execution",
|
||||
"description": "Stop a currently running execution.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"execution_id": {
|
||||
"type": "string",
|
||||
"description": "Execution ID to stop",
|
||||
"minLength": 1,
|
||||
}
|
||||
},
|
||||
"required": ["execution_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === RETRY EXECUTION ===
|
||||
{
|
||||
"name": "retry_execution",
|
||||
"method_name": "retry_execution",
|
||||
"description": "Retry a failed execution. Creates a new execution with the same input data.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"execution_id": {
|
||||
"type": "string",
|
||||
"description": "Execution ID to retry",
|
||||
"minLength": 1,
|
||||
}
|
||||
},
|
||||
"required": ["execution_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === GET EXECUTION DATA ===
|
||||
{
|
||||
"name": "get_execution_data",
|
||||
"method_name": "get_execution_data",
|
||||
"description": "Get full execution data including all node inputs and outputs. Useful for debugging.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"execution_id": {
|
||||
"type": "string",
|
||||
"description": "Execution ID",
|
||||
"minLength": 1,
|
||||
}
|
||||
},
|
||||
"required": ["execution_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
# === WAIT FOR EXECUTION ===
|
||||
{
|
||||
"name": "wait_for_execution",
|
||||
"method_name": "wait_for_execution",
|
||||
"description": "Poll an execution until it completes. Returns final status and output.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"execution_id": {
|
||||
"type": "string",
|
||||
"description": "Execution ID to wait for",
|
||||
"minLength": 1,
|
||||
},
|
||||
"timeout_seconds": {
|
||||
"type": "integer",
|
||||
"description": "Maximum seconds to wait",
|
||||
"default": 60,
|
||||
"minimum": 5,
|
||||
"maximum": 300,
|
||||
},
|
||||
"poll_interval": {
|
||||
"type": "integer",
|
||||
"description": "Seconds between status checks",
|
||||
"default": 2,
|
||||
"minimum": 1,
|
||||
"maximum": 30,
|
||||
},
|
||||
},
|
||||
"required": ["execution_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
]
|
||||
|
||||
# === HANDLER FUNCTIONS ===
|
||||
|
||||
async def list_executions(
|
||||
client: N8nClient,
|
||||
workflow_id: str | None = None,
|
||||
status: str | None = None,
|
||||
include_data: bool = False,
|
||||
limit: int = 20,
|
||||
cursor: str | None = None,
|
||||
) -> str:
|
||||
"""List workflow executions"""
|
||||
try:
|
||||
response = await client.list_executions(
|
||||
workflow_id=workflow_id,
|
||||
status=status,
|
||||
include_data=include_data,
|
||||
limit=limit,
|
||||
cursor=cursor,
|
||||
)
|
||||
|
||||
executions = response.get("data", [])
|
||||
next_cursor = response.get("nextCursor")
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"count": len(executions),
|
||||
"executions": [
|
||||
{
|
||||
"id": e.get("id"),
|
||||
"workflow_id": e.get("workflowId"),
|
||||
"workflow_name": e.get("workflowData", {}).get("name"),
|
||||
"status": e.get("status"),
|
||||
"finished": e.get("finished"),
|
||||
"started_at": e.get("startedAt"),
|
||||
"stopped_at": e.get("stoppedAt"),
|
||||
"mode": e.get("mode"),
|
||||
}
|
||||
for e in executions
|
||||
],
|
||||
"next_cursor": next_cursor,
|
||||
}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def get_execution(client: N8nClient, execution_id: str, include_data: bool = True) -> str:
|
||||
"""Get execution details"""
|
||||
try:
|
||||
execution = await client.get_execution(execution_id, include_data)
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"execution": {
|
||||
"id": execution.get("id"),
|
||||
"workflow_id": execution.get("workflowId"),
|
||||
"workflow_name": execution.get("workflowData", {}).get("name"),
|
||||
"status": execution.get("status"),
|
||||
"finished": execution.get("finished"),
|
||||
"started_at": execution.get("startedAt"),
|
||||
"stopped_at": execution.get("stoppedAt"),
|
||||
"mode": execution.get("mode"),
|
||||
"retry_of": execution.get("retryOf"),
|
||||
"retry_success_id": execution.get("retrySuccessId"),
|
||||
},
|
||||
}
|
||||
|
||||
# Include node execution data if requested
|
||||
if include_data and "data" in execution:
|
||||
result["execution"]["node_data"] = execution.get("data")
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_execution(client: N8nClient, execution_id: str) -> str:
|
||||
"""Delete a single execution"""
|
||||
try:
|
||||
await client.delete_execution(execution_id)
|
||||
|
||||
result = {"success": True, "message": f"Execution {execution_id} deleted successfully"}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_executions(client: N8nClient, execution_ids: list[str]) -> str:
|
||||
"""Bulk delete executions"""
|
||||
try:
|
||||
deleted = []
|
||||
failed = []
|
||||
|
||||
for exec_id in execution_ids:
|
||||
try:
|
||||
await client.delete_execution(exec_id)
|
||||
deleted.append(exec_id)
|
||||
except Exception as e:
|
||||
failed.append({"id": exec_id, "error": str(e)})
|
||||
|
||||
result = {
|
||||
"success": len(failed) == 0,
|
||||
"deleted_count": len(deleted),
|
||||
"deleted": deleted,
|
||||
"failed": failed if failed else None,
|
||||
}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def stop_execution(client: N8nClient, execution_id: str) -> str:
|
||||
"""Stop a running execution"""
|
||||
try:
|
||||
await client.stop_execution(execution_id)
|
||||
|
||||
result = {"success": True, "message": f"Execution {execution_id} stopped successfully"}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def retry_execution(client: N8nClient, execution_id: str) -> str:
|
||||
"""Retry a failed execution"""
|
||||
try:
|
||||
# Get the original execution to find workflow ID
|
||||
original = await client.get_execution(execution_id, include_data=True)
|
||||
|
||||
workflow_id = original.get("workflowId")
|
||||
if not workflow_id:
|
||||
raise Exception("Cannot find workflow ID from execution")
|
||||
|
||||
# Get input data from original execution
|
||||
input_data = None
|
||||
exec_data = original.get("data", {})
|
||||
if exec_data:
|
||||
# Try to get input from first node
|
||||
result_data = exec_data.get("resultData", {})
|
||||
run_data = result_data.get("runData", {})
|
||||
if run_data:
|
||||
first_node = list(run_data.keys())[0] if run_data else None
|
||||
if first_node:
|
||||
node_data = run_data[first_node]
|
||||
if node_data and len(node_data) > 0:
|
||||
input_data = node_data[0].get("data", {}).get("main", [[]])[0]
|
||||
|
||||
# Execute the workflow again
|
||||
new_execution = await client.execute_workflow(workflow_id, data=input_data)
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Retrying execution {execution_id}",
|
||||
"original_execution_id": execution_id,
|
||||
"new_execution": {
|
||||
"id": new_execution.get("id"),
|
||||
"workflow_id": workflow_id,
|
||||
"status": new_execution.get("status", "running"),
|
||||
},
|
||||
}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def get_execution_data(client: N8nClient, execution_id: str) -> str:
|
||||
"""Get full execution data"""
|
||||
try:
|
||||
execution = await client.get_execution(execution_id, include_data=True)
|
||||
|
||||
exec_data = execution.get("data", {})
|
||||
result_data = exec_data.get("resultData", {})
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"execution_id": execution_id,
|
||||
"status": execution.get("status"),
|
||||
"finished": execution.get("finished"),
|
||||
"run_data": result_data.get("runData", {}),
|
||||
"last_node_executed": result_data.get("lastNodeExecuted"),
|
||||
"error": result_data.get("error"),
|
||||
}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def wait_for_execution(
|
||||
client: N8nClient, execution_id: str, timeout_seconds: int = 60, poll_interval: int = 2
|
||||
) -> str:
|
||||
"""Wait for execution to complete"""
|
||||
try:
|
||||
elapsed = 0
|
||||
final_status = None
|
||||
execution = None
|
||||
|
||||
while elapsed < timeout_seconds:
|
||||
execution = await client.get_execution(execution_id, include_data=True)
|
||||
status = execution.get("status")
|
||||
finished = execution.get("finished")
|
||||
|
||||
if finished or status in ["success", "error", "crashed"]:
|
||||
final_status = status
|
||||
break
|
||||
|
||||
await asyncio.sleep(poll_interval)
|
||||
elapsed += poll_interval
|
||||
|
||||
if not final_status:
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"error": f"Timeout after {timeout_seconds} seconds",
|
||||
"execution_id": execution_id,
|
||||
"last_status": execution.get("status") if execution else "unknown",
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"execution_id": execution_id,
|
||||
"final_status": final_status,
|
||||
"finished": execution.get("finished"),
|
||||
"started_at": execution.get("startedAt"),
|
||||
"stopped_at": execution.get("stoppedAt"),
|
||||
"duration_seconds": elapsed,
|
||||
}
|
||||
|
||||
# Include error if failed
|
||||
if final_status == "error":
|
||||
exec_data = execution.get("data", {})
|
||||
result_data = exec_data.get("resultData", {})
|
||||
result["error"] = result_data.get("error")
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
228
plugins/n8n/handlers/projects.py
Normal file
228
plugins/n8n/handlers/projects.py
Normal file
@@ -0,0 +1,228 @@
|
||||
"""Projects Handler - manages n8n projects (Enterprise/Pro)"""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.n8n.client import N8nClient
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator"""
|
||||
return [
|
||||
{
|
||||
"name": "list_projects",
|
||||
"method_name": "list_projects",
|
||||
"description": "[Enterprise] List all projects. Requires n8n Enterprise/Pro license. All parameters are OPTIONAL.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {"type": "integer", "default": 100, "minimum": 1, "maximum": 250},
|
||||
"cursor": {"type": "string", "description": "OPTIONAL: Pagination cursor."},
|
||||
},
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_project",
|
||||
"method_name": "get_project",
|
||||
"description": "[Enterprise] Get project details by ID. Requires n8n Enterprise/Pro license.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"project_id": {"type": "string", "minLength": 1}},
|
||||
"required": ["project_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_project",
|
||||
"method_name": "create_project",
|
||||
"description": "[Enterprise] Create a new project. Requires n8n Enterprise/Pro license.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string", "minLength": 1, "description": "Project name"}
|
||||
},
|
||||
"required": ["name"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "update_project",
|
||||
"method_name": "update_project",
|
||||
"description": "[Enterprise] Update project metadata. Requires n8n Enterprise/Pro license.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "minLength": 1},
|
||||
"name": {"type": "string", "minLength": 1, "description": "New project name"},
|
||||
},
|
||||
"required": ["project_id", "name"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "delete_project",
|
||||
"method_name": "delete_project",
|
||||
"description": "[Enterprise] Delete a project. Requires n8n Enterprise/Pro license.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"project_id": {"type": "string", "minLength": 1}},
|
||||
"required": ["project_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "add_project_users",
|
||||
"method_name": "add_project_users",
|
||||
"description": "[Enterprise] Add users to a project with roles. Requires n8n Enterprise/Pro license.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "minLength": 1},
|
||||
"users": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_id": {"type": "string"},
|
||||
"role": {
|
||||
"type": "string",
|
||||
"enum": ["project:admin", "project:editor", "project:viewer"],
|
||||
},
|
||||
},
|
||||
"required": ["user_id", "role"],
|
||||
},
|
||||
"minItems": 1,
|
||||
},
|
||||
},
|
||||
"required": ["project_id", "users"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "change_project_user_role",
|
||||
"method_name": "change_project_user_role",
|
||||
"description": "[Enterprise] Change a user's role in a project. Requires n8n Enterprise/Pro license.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "minLength": 1},
|
||||
"user_id": {"type": "string", "minLength": 1},
|
||||
"role": {
|
||||
"type": "string",
|
||||
"enum": ["project:admin", "project:editor", "project:viewer"],
|
||||
},
|
||||
},
|
||||
"required": ["project_id", "user_id", "role"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "remove_project_user",
|
||||
"method_name": "remove_project_user",
|
||||
"description": "[Enterprise] Remove a user from a project. Requires n8n Enterprise/Pro license.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "minLength": 1},
|
||||
"user_id": {"type": "string", "minLength": 1},
|
||||
},
|
||||
"required": ["project_id", "user_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
]
|
||||
|
||||
async def list_projects(client: N8nClient, limit: int = 100, cursor: str | None = None) -> str:
|
||||
try:
|
||||
response = await client.list_projects(limit=limit, cursor=cursor)
|
||||
projects = response.get("data", [])
|
||||
result = {
|
||||
"success": True,
|
||||
"count": len(projects),
|
||||
"projects": [{"id": p.get("id"), "name": p.get("name")} for p in projects],
|
||||
"next_cursor": response.get("nextCursor"),
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def get_project(client: N8nClient, project_id: str) -> str:
|
||||
try:
|
||||
project = await client.get_project(project_id)
|
||||
return json.dumps(
|
||||
{"success": True, "project": {"id": project.get("id"), "name": project.get("name")}},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def create_project(client: N8nClient, name: str) -> str:
|
||||
try:
|
||||
project = await client.create_project(name)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Project '{name}' created",
|
||||
"project": {"id": project.get("id"), "name": project.get("name")},
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def update_project(client: N8nClient, project_id: str, name: str) -> str:
|
||||
try:
|
||||
project = await client.update_project(project_id, name)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Project updated to '{name}'",
|
||||
"project": {"id": project.get("id"), "name": project.get("name")},
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_project(client: N8nClient, project_id: str) -> str:
|
||||
try:
|
||||
await client.delete_project(project_id)
|
||||
return json.dumps({"success": True, "message": f"Project {project_id} deleted"}, indent=2)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def add_project_users(client: N8nClient, project_id: str, users: list[dict[str, str]]) -> str:
|
||||
try:
|
||||
relations = [{"userId": u["user_id"], "role": u["role"]} for u in users]
|
||||
await client.add_project_users(project_id, relations)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"Added {len(users)} user(s) to project {project_id}"},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def change_project_user_role(
|
||||
client: N8nClient, project_id: str, user_id: str, role: str
|
||||
) -> str:
|
||||
try:
|
||||
await client.change_project_user_role(project_id, user_id, role)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"User {user_id} role changed to {role} in project {project_id}",
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def remove_project_user(client: N8nClient, project_id: str, user_id: str) -> str:
|
||||
try:
|
||||
await client.remove_project_user(project_id, user_id)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"User {user_id} removed from project {project_id}"},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
150
plugins/n8n/handlers/system.py
Normal file
150
plugins/n8n/handlers/system.py
Normal file
@@ -0,0 +1,150 @@
|
||||
"""System Handler - manages n8n system operations (audit, source control, health)"""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.n8n.client import N8nClient
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator"""
|
||||
return [
|
||||
{
|
||||
"name": "run_security_audit",
|
||||
"method_name": "run_security_audit",
|
||||
"description": "Run a security audit on the n8n instance. Returns security diagnostics grouped by category. All parameters are OPTIONAL.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"categories": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "OPTIONAL: Specific categories to audit. Omit for all categories.",
|
||||
}
|
||||
},
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "source_control_pull",
|
||||
"method_name": "source_control_pull",
|
||||
"description": "[Enterprise] Pull workflows from source control (Git). Syncs workflows from connected repository. Requires n8n Enterprise/Pro license. All parameters are OPTIONAL.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"variables": {
|
||||
"type": "object",
|
||||
"description": "OPTIONAL: Variables to set during pull. Omit if not needed.",
|
||||
},
|
||||
"force": {
|
||||
"type": "boolean",
|
||||
"description": "Force pull even if conflicts exist",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "get_instance_info",
|
||||
"method_name": "get_instance_info",
|
||||
"description": "Get n8n instance information including version and configuration.",
|
||||
"schema": {"type": "object", "properties": {}},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "health_check",
|
||||
"method_name": "health_check",
|
||||
"description": "Check if the n8n instance is healthy and accessible.",
|
||||
"schema": {"type": "object", "properties": {}},
|
||||
"scope": "read",
|
||||
},
|
||||
]
|
||||
|
||||
async def run_security_audit(client: N8nClient, categories: list[str] | None = None) -> str:
|
||||
"""Run security audit"""
|
||||
try:
|
||||
result = await client.run_audit(categories)
|
||||
|
||||
# Parse audit results
|
||||
audit_data = {"success": True, "audit_results": result}
|
||||
|
||||
# Extract summary if available
|
||||
if isinstance(result, dict):
|
||||
risk_report = result.get("risk", {})
|
||||
if risk_report:
|
||||
audit_data["summary"] = {
|
||||
"risk_categories": list(risk_report.keys()),
|
||||
"total_issues": sum(
|
||||
len(issues) if isinstance(issues, list) else 0
|
||||
for issues in risk_report.values()
|
||||
),
|
||||
}
|
||||
|
||||
return json.dumps(audit_data, indent=2)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def source_control_pull(
|
||||
client: N8nClient, variables: dict[str, str] | None = None, force: bool = False
|
||||
) -> str:
|
||||
"""Pull from source control"""
|
||||
try:
|
||||
result = await client.source_control_pull(variables=variables, force=force)
|
||||
|
||||
return json.dumps(
|
||||
{"success": True, "message": "Source control pull completed", "result": result},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def get_instance_info(client: N8nClient) -> str:
|
||||
"""Get instance information"""
|
||||
try:
|
||||
# Try multiple endpoints to gather instance info
|
||||
info = {}
|
||||
|
||||
# Get version info from settings or health
|
||||
try:
|
||||
health = await client.health_check()
|
||||
info["health"] = health
|
||||
except:
|
||||
pass
|
||||
|
||||
# Get current user to verify connectivity
|
||||
try:
|
||||
user = await client.request("GET", "user")
|
||||
info["current_user"] = {
|
||||
"id": user.get("id"),
|
||||
"email": user.get("email"),
|
||||
"role": user.get("role"),
|
||||
}
|
||||
except:
|
||||
pass
|
||||
|
||||
info["instance_url"] = client.site_url
|
||||
info["api_base"] = client.api_base
|
||||
|
||||
return json.dumps({"success": True, "instance_info": info}, indent=2)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def health_check(client: N8nClient) -> str:
|
||||
"""Check instance health"""
|
||||
try:
|
||||
result = await client.health_check()
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"healthy": result.get("healthy", False),
|
||||
"status": result.get("status", "unknown"),
|
||||
"instance_url": client.site_url,
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps(
|
||||
{"success": False, "healthy": False, "error": str(e), "instance_url": client.site_url},
|
||||
indent=2,
|
||||
)
|
||||
160
plugins/n8n/handlers/tags.py
Normal file
160
plugins/n8n/handlers/tags.py
Normal file
@@ -0,0 +1,160 @@
|
||||
"""Tags Handler - manages n8n tags"""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.n8n.client import N8nClient
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator"""
|
||||
return [
|
||||
{
|
||||
"name": "list_tags",
|
||||
"method_name": "list_tags",
|
||||
"description": "List all tags used for workflow organization. All parameters are OPTIONAL.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {"type": "integer", "default": 100, "minimum": 1, "maximum": 250},
|
||||
"cursor": {"type": "string", "description": "OPTIONAL: Pagination cursor."},
|
||||
},
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_tag",
|
||||
"method_name": "get_tag",
|
||||
"description": "Get tag details by ID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"tag_id": {"type": "string", "minLength": 1}},
|
||||
"required": ["tag_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_tag",
|
||||
"method_name": "create_tag",
|
||||
"description": "Create a new tag for workflow organization.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string", "minLength": 1, "description": "Tag name"}
|
||||
},
|
||||
"required": ["name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_tag",
|
||||
"method_name": "update_tag",
|
||||
"description": "Update tag name.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tag_id": {"type": "string", "minLength": 1},
|
||||
"name": {"type": "string", "minLength": 1, "description": "New tag name"},
|
||||
},
|
||||
"required": ["tag_id", "name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_tag",
|
||||
"method_name": "delete_tag",
|
||||
"description": "Delete a tag.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"tag_id": {"type": "string", "minLength": 1}},
|
||||
"required": ["tag_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_tags",
|
||||
"method_name": "delete_tags",
|
||||
"description": "Bulk delete multiple tags.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tag_ids": {"type": "array", "items": {"type": "string"}, "minItems": 1}
|
||||
},
|
||||
"required": ["tag_ids"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
]
|
||||
|
||||
async def list_tags(client: N8nClient, limit: int = 100, cursor: str | None = None) -> str:
|
||||
try:
|
||||
response = await client.list_tags(limit=limit, cursor=cursor)
|
||||
tags = response.get("data", [])
|
||||
result = {
|
||||
"success": True,
|
||||
"count": len(tags),
|
||||
"tags": [{"id": t.get("id"), "name": t.get("name")} for t in tags],
|
||||
"next_cursor": response.get("nextCursor"),
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def get_tag(client: N8nClient, tag_id: str) -> str:
|
||||
try:
|
||||
tag = await client.get_tag(tag_id)
|
||||
return json.dumps(
|
||||
{"success": True, "tag": {"id": tag.get("id"), "name": tag.get("name")}}, indent=2
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def create_tag(client: N8nClient, name: str) -> str:
|
||||
try:
|
||||
tag = await client.create_tag(name)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Tag '{name}' created",
|
||||
"tag": {"id": tag.get("id"), "name": tag.get("name")},
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def update_tag(client: N8nClient, tag_id: str, name: str) -> str:
|
||||
try:
|
||||
tag = await client.update_tag(tag_id, name)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Tag updated to '{name}'",
|
||||
"tag": {"id": tag.get("id"), "name": tag.get("name")},
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_tag(client: N8nClient, tag_id: str) -> str:
|
||||
try:
|
||||
await client.delete_tag(tag_id)
|
||||
return json.dumps({"success": True, "message": f"Tag {tag_id} deleted"}, indent=2)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_tags(client: N8nClient, tag_ids: list[str]) -> str:
|
||||
try:
|
||||
deleted, failed = [], []
|
||||
for tid in tag_ids:
|
||||
try:
|
||||
await client.delete_tag(tid)
|
||||
deleted.append(tid)
|
||||
except Exception as e:
|
||||
failed.append({"id": tid, "error": str(e)})
|
||||
return json.dumps(
|
||||
{"success": len(failed) == 0, "deleted": deleted, "failed": failed if failed else None},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
167
plugins/n8n/handlers/users.py
Normal file
167
plugins/n8n/handlers/users.py
Normal file
@@ -0,0 +1,167 @@
|
||||
"""Users Handler - manages n8n users"""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.n8n.client import N8nClient
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator"""
|
||||
return [
|
||||
{
|
||||
"name": "list_users",
|
||||
"method_name": "list_users",
|
||||
"description": "List all users in the n8n instance. All parameters are OPTIONAL.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {"type": "integer", "default": 100, "minimum": 1, "maximum": 250},
|
||||
"cursor": {"type": "string", "description": "OPTIONAL: Pagination cursor."},
|
||||
"include_role": {"type": "boolean", "default": True},
|
||||
},
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "get_user",
|
||||
"method_name": "get_user",
|
||||
"description": "Get user details by ID or email.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_id": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "User ID or email",
|
||||
},
|
||||
"include_role": {"type": "boolean", "default": True},
|
||||
},
|
||||
"required": ["user_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "create_user",
|
||||
"method_name": "create_user",
|
||||
"description": "Invite/create a new user.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {"type": "string", "format": "email", "description": "User email"},
|
||||
"role": {
|
||||
"type": "string",
|
||||
"enum": ["global:owner", "global:admin", "global:member"],
|
||||
"default": "global:member",
|
||||
},
|
||||
},
|
||||
"required": ["email"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "delete_user",
|
||||
"method_name": "delete_user",
|
||||
"description": "Delete a user from the instance.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"user_id": {"type": "string", "minLength": 1}},
|
||||
"required": ["user_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "change_user_role",
|
||||
"method_name": "change_user_role",
|
||||
"description": "Change a user's global role.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_id": {"type": "string", "minLength": 1},
|
||||
"new_role": {
|
||||
"type": "string",
|
||||
"enum": ["global:owner", "global:admin", "global:member"],
|
||||
"description": "New role for the user",
|
||||
},
|
||||
},
|
||||
"required": ["user_id", "new_role"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
]
|
||||
|
||||
async def list_users(
|
||||
client: N8nClient, limit: int = 100, cursor: str | None = None, include_role: bool = True
|
||||
) -> str:
|
||||
try:
|
||||
response = await client.list_users(limit=limit, cursor=cursor, include_role=include_role)
|
||||
users = response.get("data", [])
|
||||
result = {
|
||||
"success": True,
|
||||
"count": len(users),
|
||||
"users": [
|
||||
{
|
||||
"id": u.get("id"),
|
||||
"email": u.get("email"),
|
||||
"first_name": u.get("firstName"),
|
||||
"last_name": u.get("lastName"),
|
||||
"role": u.get("role"),
|
||||
"is_pending": u.get("isPending"),
|
||||
}
|
||||
for u in users
|
||||
],
|
||||
"next_cursor": response.get("nextCursor"),
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def get_user(client: N8nClient, user_id: str, include_role: bool = True) -> str:
|
||||
try:
|
||||
user = await client.get_user(user_id, include_role)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"user": {
|
||||
"id": user.get("id"),
|
||||
"email": user.get("email"),
|
||||
"first_name": user.get("firstName"),
|
||||
"last_name": user.get("lastName"),
|
||||
"role": user.get("role"),
|
||||
"is_pending": user.get("isPending"),
|
||||
},
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def create_user(client: N8nClient, email: str, role: str = "global:member") -> str:
|
||||
try:
|
||||
users = [{"email": email, "role": role}]
|
||||
response = await client.create_user(users)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"User {email} invited successfully",
|
||||
"users": response.get("data", []),
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_user(client: N8nClient, user_id: str) -> str:
|
||||
try:
|
||||
await client.delete_user(user_id)
|
||||
return json.dumps({"success": True, "message": f"User {user_id} deleted"}, indent=2)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def change_user_role(client: N8nClient, user_id: str, new_role: str) -> str:
|
||||
try:
|
||||
await client.change_user_role(user_id, new_role)
|
||||
return json.dumps(
|
||||
{"success": True, "message": f"User {user_id} role changed to {new_role}"}, indent=2
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
186
plugins/n8n/handlers/variables.py
Normal file
186
plugins/n8n/handlers/variables.py
Normal file
@@ -0,0 +1,186 @@
|
||||
"""Variables Handler - manages n8n environment variables"""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.n8n.client import N8nClient
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator"""
|
||||
return [
|
||||
{
|
||||
"name": "list_variables",
|
||||
"method_name": "list_variables",
|
||||
"description": "[Enterprise] List all environment variables. Requires n8n Enterprise/Pro license. All parameters are OPTIONAL.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {"type": "integer", "default": 100, "minimum": 1, "maximum": 250},
|
||||
"cursor": {"type": "string", "description": "OPTIONAL: Pagination cursor."},
|
||||
},
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_variable",
|
||||
"method_name": "get_variable",
|
||||
"description": "[Enterprise] Get variable value by key. Requires n8n Enterprise/Pro license.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {"type": "string", "minLength": 1, "description": "Variable key"}
|
||||
},
|
||||
"required": ["key"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_variable",
|
||||
"method_name": "create_variable",
|
||||
"description": "[Enterprise] Create a new environment variable. Requires n8n Enterprise/Pro license.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {"type": "string", "minLength": 1, "description": "Variable key"},
|
||||
"value": {"type": "string", "description": "Variable value"},
|
||||
},
|
||||
"required": ["key", "value"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "update_variable",
|
||||
"method_name": "update_variable",
|
||||
"description": "[Enterprise] Update an existing variable's value. Requires n8n Enterprise/Pro license.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {"type": "string", "minLength": 1},
|
||||
"value": {"type": "string", "description": "New value"},
|
||||
},
|
||||
"required": ["key", "value"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "delete_variable",
|
||||
"method_name": "delete_variable",
|
||||
"description": "[Enterprise] Delete an environment variable. Requires n8n Enterprise/Pro license.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"key": {"type": "string", "minLength": 1}},
|
||||
"required": ["key"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "set_variables",
|
||||
"method_name": "set_variables",
|
||||
"description": "[Enterprise] Bulk set multiple variables at once. Requires n8n Enterprise/Pro license.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"variables": {
|
||||
"type": "object",
|
||||
"description": "Key-value pairs of variables to set",
|
||||
"additionalProperties": {"type": "string"},
|
||||
}
|
||||
},
|
||||
"required": ["variables"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
]
|
||||
|
||||
async def list_variables(client: N8nClient, limit: int = 100, cursor: str | None = None) -> str:
|
||||
try:
|
||||
response = await client.list_variables(limit=limit, cursor=cursor)
|
||||
variables = response.get("data", [])
|
||||
result = {
|
||||
"success": True,
|
||||
"count": len(variables),
|
||||
"variables": [{"key": v.get("key"), "value": v.get("value")} for v in variables],
|
||||
"next_cursor": response.get("nextCursor"),
|
||||
}
|
||||
return json.dumps(result, indent=2)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def get_variable(client: N8nClient, key: str) -> str:
|
||||
try:
|
||||
variable = await client.get_variable(key)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"variable": {"key": variable.get("key"), "value": variable.get("value")},
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def create_variable(client: N8nClient, key: str, value: str) -> str:
|
||||
try:
|
||||
await client.create_variable(key, value)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Variable '{key}' created",
|
||||
"variable": {"key": key, "value": value},
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def update_variable(client: N8nClient, key: str, value: str) -> str:
|
||||
try:
|
||||
await client.update_variable(key, value)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"Variable '{key}' updated",
|
||||
"variable": {"key": key, "value": value},
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_variable(client: N8nClient, key: str) -> str:
|
||||
try:
|
||||
await client.delete_variable(key)
|
||||
return json.dumps({"success": True, "message": f"Variable '{key}' deleted"}, indent=2)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def set_variables(client: N8nClient, variables: dict[str, str]) -> str:
|
||||
try:
|
||||
created, updated, failed = [], [], []
|
||||
|
||||
for key, value in variables.items():
|
||||
try:
|
||||
# Try to get existing variable
|
||||
try:
|
||||
await client.get_variable(key)
|
||||
# Variable exists, update it
|
||||
await client.update_variable(key, value)
|
||||
updated.append(key)
|
||||
except:
|
||||
# Variable doesn't exist, create it
|
||||
await client.create_variable(key, value)
|
||||
created.append(key)
|
||||
except Exception as e:
|
||||
failed.append({"key": key, "error": str(e)})
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": len(failed) == 0,
|
||||
"created": created,
|
||||
"updated": updated,
|
||||
"failed": failed if failed else None,
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
680
plugins/n8n/handlers/workflows.py
Normal file
680
plugins/n8n/handlers/workflows.py
Normal file
@@ -0,0 +1,680 @@
|
||||
"""Workflow Handler - manages n8n workflows"""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.n8n.client import N8nClient
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator"""
|
||||
return [
|
||||
# === LIST WORKFLOWS ===
|
||||
{
|
||||
"name": "list_workflows",
|
||||
"method_name": "list_workflows",
|
||||
"description": "List all n8n workflows with optional filters. Returns workflow ID, name, active status, tags, and metadata. All filter parameters are OPTIONAL.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"active": {
|
||||
"type": "boolean",
|
||||
"description": "OPTIONAL: Filter by active/inactive status. Omit for all workflows.",
|
||||
},
|
||||
"tags": {
|
||||
"type": "string",
|
||||
"description": "OPTIONAL: Filter by tag name(s), comma-separated. Omit for all workflows.",
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "OPTIONAL: Filter by workflow name (partial match). Omit for all workflows.",
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Maximum workflows to return",
|
||||
"default": 50,
|
||||
"minimum": 1,
|
||||
"maximum": 250,
|
||||
},
|
||||
"cursor": {
|
||||
"type": "string",
|
||||
"description": "OPTIONAL: Pagination cursor for next page.",
|
||||
},
|
||||
},
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
# === GET WORKFLOW ===
|
||||
{
|
||||
"name": "get_workflow",
|
||||
"method_name": "get_workflow",
|
||||
"description": "Get detailed information about a specific workflow including nodes, connections, and settings.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workflow_id": {"type": "string", "description": "Workflow ID", "minLength": 1}
|
||||
},
|
||||
"required": ["workflow_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
# === CREATE WORKFLOW ===
|
||||
{
|
||||
"name": "create_workflow",
|
||||
"method_name": "create_workflow",
|
||||
"description": "Create a new workflow from JSON definition. Workflow will be inactive by default. Note: settings and static_data are OPTIONAL - omit them entirely if not needed.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string", "description": "Workflow name", "minLength": 1},
|
||||
"nodes": {
|
||||
"type": "array",
|
||||
"description": "Array of node definitions",
|
||||
"items": {"type": "object"},
|
||||
"default": [],
|
||||
},
|
||||
"connections": {
|
||||
"type": "object",
|
||||
"description": "Node connections definition",
|
||||
"default": {},
|
||||
},
|
||||
"settings": {
|
||||
"type": "object",
|
||||
"description": "OPTIONAL: Workflow settings (timezone, error workflow, etc.). Omit this parameter if not needed.",
|
||||
"default": {},
|
||||
},
|
||||
"static_data": {
|
||||
"type": "object",
|
||||
"description": "OPTIONAL: Static data for the workflow. Omit this parameter if not needed.",
|
||||
"default": {},
|
||||
},
|
||||
},
|
||||
"required": ["name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === UPDATE WORKFLOW ===
|
||||
{
|
||||
"name": "update_workflow",
|
||||
"method_name": "update_workflow",
|
||||
"description": "Update an existing workflow. Can modify nodes, connections, settings, or name. All parameters except workflow_id are OPTIONAL - omit them to keep current values.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workflow_id": {
|
||||
"type": "string",
|
||||
"description": "Workflow ID to update",
|
||||
"minLength": 1,
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "OPTIONAL: New workflow name. Omit to keep current.",
|
||||
},
|
||||
"nodes": {
|
||||
"type": "array",
|
||||
"items": {"type": "object"},
|
||||
"description": "OPTIONAL: Updated node definitions. Omit to keep current.",
|
||||
},
|
||||
"connections": {
|
||||
"type": "object",
|
||||
"description": "OPTIONAL: Updated connections. Omit to keep current.",
|
||||
},
|
||||
"settings": {
|
||||
"type": "object",
|
||||
"description": "OPTIONAL: Updated settings. Omit to keep current.",
|
||||
},
|
||||
},
|
||||
"required": ["workflow_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === DELETE WORKFLOW ===
|
||||
{
|
||||
"name": "delete_workflow",
|
||||
"method_name": "delete_workflow",
|
||||
"description": "Permanently delete a workflow. This action cannot be undone.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workflow_id": {
|
||||
"type": "string",
|
||||
"description": "Workflow ID to delete",
|
||||
"minLength": 1,
|
||||
}
|
||||
},
|
||||
"required": ["workflow_id"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
# === ACTIVATE WORKFLOW ===
|
||||
{
|
||||
"name": "activate_workflow",
|
||||
"method_name": "activate_workflow",
|
||||
"description": "Activate a workflow so it runs automatically when triggered. Workflow must have at least one trigger node.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workflow_id": {
|
||||
"type": "string",
|
||||
"description": "Workflow ID to activate",
|
||||
"minLength": 1,
|
||||
}
|
||||
},
|
||||
"required": ["workflow_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === DEACTIVATE WORKFLOW ===
|
||||
{
|
||||
"name": "deactivate_workflow",
|
||||
"method_name": "deactivate_workflow",
|
||||
"description": "Deactivate a workflow. It will no longer run automatically but can still be executed manually.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workflow_id": {
|
||||
"type": "string",
|
||||
"description": "Workflow ID to deactivate",
|
||||
"minLength": 1,
|
||||
}
|
||||
},
|
||||
"required": ["workflow_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === EXECUTE WORKFLOW ===
|
||||
{
|
||||
"name": "execute_workflow",
|
||||
"method_name": "execute_workflow",
|
||||
"description": "Manually execute a workflow and return execution ID. Use get_execution to check status and results.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workflow_id": {
|
||||
"type": "string",
|
||||
"description": "Workflow ID to execute",
|
||||
"minLength": 1,
|
||||
}
|
||||
},
|
||||
"required": ["workflow_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === EXECUTE WORKFLOW WITH DATA ===
|
||||
{
|
||||
"name": "execute_workflow_with_data",
|
||||
"method_name": "execute_workflow_with_data",
|
||||
"description": "Execute workflow with custom input data. Useful for workflows with webhook or manual triggers.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workflow_id": {
|
||||
"type": "string",
|
||||
"description": "Workflow ID to execute",
|
||||
"minLength": 1,
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"description": "Input data to pass to workflow trigger node",
|
||||
},
|
||||
},
|
||||
"required": ["workflow_id", "data"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === DUPLICATE WORKFLOW ===
|
||||
{
|
||||
"name": "duplicate_workflow",
|
||||
"method_name": "duplicate_workflow",
|
||||
"description": "Create a copy of an existing workflow with a new name.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workflow_id": {
|
||||
"type": "string",
|
||||
"description": "Workflow ID to duplicate",
|
||||
"minLength": 1,
|
||||
},
|
||||
"new_name": {
|
||||
"type": "string",
|
||||
"description": "Name for the duplicated workflow",
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
"required": ["workflow_id", "new_name"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === EXPORT WORKFLOW ===
|
||||
{
|
||||
"name": "export_workflow",
|
||||
"method_name": "export_workflow",
|
||||
"description": "Export a workflow as JSON. Can be used for backup or sharing.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workflow_id": {
|
||||
"type": "string",
|
||||
"description": "Workflow ID to export",
|
||||
"minLength": 1,
|
||||
}
|
||||
},
|
||||
"required": ["workflow_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
# === IMPORT WORKFLOW ===
|
||||
{
|
||||
"name": "import_workflow",
|
||||
"method_name": "import_workflow",
|
||||
"description": "Import a workflow from JSON definition. Similar to create but accepts full workflow JSON.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workflow_json": {
|
||||
"type": "object",
|
||||
"description": "Full workflow JSON to import",
|
||||
},
|
||||
"name_override": {
|
||||
"type": "string",
|
||||
"description": "OPTIONAL: Override the workflow name from JSON. Omit to use the name from JSON.",
|
||||
},
|
||||
},
|
||||
"required": ["workflow_json"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
# === GET WORKFLOW TAGS ===
|
||||
{
|
||||
"name": "get_workflow_tags",
|
||||
"method_name": "get_workflow_tags",
|
||||
"description": "Get list of tags assigned to a workflow.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workflow_id": {"type": "string", "description": "Workflow ID", "minLength": 1}
|
||||
},
|
||||
"required": ["workflow_id"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
# === SET WORKFLOW TAGS ===
|
||||
{
|
||||
"name": "set_workflow_tags",
|
||||
"method_name": "set_workflow_tags",
|
||||
"description": "Assign tags to a workflow. Replaces existing tags. Pass empty array [] to remove all tags.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workflow_id": {"type": "string", "description": "Workflow ID", "minLength": 1},
|
||||
"tag_ids": {
|
||||
"type": "array",
|
||||
"description": "List of tag IDs to assign. Use empty array [] to remove all tags. Get tag IDs using n8n_list_tags first.",
|
||||
"items": {"type": "string"},
|
||||
"default": [],
|
||||
},
|
||||
},
|
||||
"required": ["workflow_id"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
]
|
||||
|
||||
# === HANDLER FUNCTIONS ===
|
||||
|
||||
async def list_workflows(
|
||||
client: N8nClient,
|
||||
active: bool | None = None,
|
||||
tags: str | None = None,
|
||||
name: str | None = None,
|
||||
limit: int = 50,
|
||||
cursor: str | None = None,
|
||||
) -> str:
|
||||
"""List all workflows with filters"""
|
||||
try:
|
||||
response = await client.list_workflows(
|
||||
active=active, tags=tags, name=name, limit=limit, cursor=cursor
|
||||
)
|
||||
|
||||
workflows = response.get("data", [])
|
||||
next_cursor = response.get("nextCursor")
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"count": len(workflows),
|
||||
"workflows": [
|
||||
{
|
||||
"id": w.get("id"),
|
||||
"name": w.get("name"),
|
||||
"active": w.get("active"),
|
||||
"tags": [t.get("name") for t in w.get("tags", [])],
|
||||
"created_at": w.get("createdAt"),
|
||||
"updated_at": w.get("updatedAt"),
|
||||
}
|
||||
for w in workflows
|
||||
],
|
||||
"next_cursor": next_cursor,
|
||||
}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def get_workflow(client: N8nClient, workflow_id: str) -> str:
|
||||
"""Get workflow details"""
|
||||
try:
|
||||
workflow = await client.get_workflow(workflow_id)
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"workflow": {
|
||||
"id": workflow.get("id"),
|
||||
"name": workflow.get("name"),
|
||||
"active": workflow.get("active"),
|
||||
"nodes": workflow.get("nodes", []),
|
||||
"connections": workflow.get("connections", {}),
|
||||
"settings": workflow.get("settings", {}),
|
||||
"static_data": workflow.get("staticData"),
|
||||
"tags": [t.get("name") for t in workflow.get("tags", [])],
|
||||
"created_at": workflow.get("createdAt"),
|
||||
"updated_at": workflow.get("updatedAt"),
|
||||
},
|
||||
}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def create_workflow(
|
||||
client: N8nClient,
|
||||
name: str,
|
||||
nodes: list[dict[str, Any]] | None = None,
|
||||
connections: dict[str, Any] | None = None,
|
||||
settings: dict[str, Any] | None = None,
|
||||
static_data: dict[str, Any] | None = None,
|
||||
) -> str:
|
||||
"""Create a new workflow"""
|
||||
try:
|
||||
# Use defaults for optional parameters
|
||||
data = {
|
||||
"name": name,
|
||||
"nodes": nodes if nodes is not None else [],
|
||||
"connections": connections if connections is not None else {},
|
||||
}
|
||||
|
||||
# Only add settings/static_data if provided and non-empty
|
||||
if settings and isinstance(settings, dict) and len(settings) > 0:
|
||||
data["settings"] = settings
|
||||
if static_data and isinstance(static_data, dict) and len(static_data) > 0:
|
||||
data["staticData"] = static_data
|
||||
|
||||
workflow = await client.create_workflow(data)
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Workflow '{name}' created successfully",
|
||||
"workflow": {
|
||||
"id": workflow.get("id"),
|
||||
"name": workflow.get("name"),
|
||||
"active": workflow.get("active", False),
|
||||
},
|
||||
}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def update_workflow(
|
||||
client: N8nClient,
|
||||
workflow_id: str,
|
||||
name: str | None = None,
|
||||
nodes: list[dict[str, Any]] | None = None,
|
||||
connections: dict[str, Any] | None = None,
|
||||
settings: dict[str, Any] | None = None,
|
||||
) -> str:
|
||||
"""Update an existing workflow"""
|
||||
try:
|
||||
# Get current workflow first
|
||||
current = await client.get_workflow(workflow_id)
|
||||
|
||||
# Build update data
|
||||
data = {
|
||||
"name": name or current.get("name"),
|
||||
"nodes": nodes or current.get("nodes", []),
|
||||
"connections": connections or current.get("connections", {}),
|
||||
}
|
||||
|
||||
if settings:
|
||||
data["settings"] = settings
|
||||
|
||||
workflow = await client.update_workflow(workflow_id, data)
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Workflow '{workflow.get('name')}' updated successfully",
|
||||
"workflow": {
|
||||
"id": workflow.get("id"),
|
||||
"name": workflow.get("name"),
|
||||
"active": workflow.get("active"),
|
||||
},
|
||||
}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def delete_workflow(client: N8nClient, workflow_id: str) -> str:
|
||||
"""Delete a workflow"""
|
||||
try:
|
||||
await client.delete_workflow(workflow_id)
|
||||
|
||||
result = {"success": True, "message": f"Workflow {workflow_id} deleted successfully"}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def activate_workflow(client: N8nClient, workflow_id: str) -> str:
|
||||
"""Activate a workflow"""
|
||||
try:
|
||||
workflow = await client.activate_workflow(workflow_id)
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Workflow '{workflow.get('name')}' activated successfully",
|
||||
"workflow": {
|
||||
"id": workflow.get("id"),
|
||||
"name": workflow.get("name"),
|
||||
"active": workflow.get("active"),
|
||||
},
|
||||
}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def deactivate_workflow(client: N8nClient, workflow_id: str) -> str:
|
||||
"""Deactivate a workflow"""
|
||||
try:
|
||||
workflow = await client.deactivate_workflow(workflow_id)
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Workflow '{workflow.get('name')}' deactivated successfully",
|
||||
"workflow": {
|
||||
"id": workflow.get("id"),
|
||||
"name": workflow.get("name"),
|
||||
"active": workflow.get("active"),
|
||||
},
|
||||
}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def execute_workflow(client: N8nClient, workflow_id: str) -> str:
|
||||
"""Execute a workflow manually"""
|
||||
try:
|
||||
execution = await client.execute_workflow(workflow_id)
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"message": "Workflow execution started",
|
||||
"execution": {
|
||||
"id": execution.get("id"),
|
||||
"workflow_id": workflow_id,
|
||||
"status": execution.get("status", "running"),
|
||||
},
|
||||
}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def execute_workflow_with_data(
|
||||
client: N8nClient, workflow_id: str, data: dict[str, Any]
|
||||
) -> str:
|
||||
"""Execute workflow with custom input data"""
|
||||
try:
|
||||
execution = await client.execute_workflow(workflow_id, data=data)
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"message": "Workflow execution started with custom data",
|
||||
"execution": {
|
||||
"id": execution.get("id"),
|
||||
"workflow_id": workflow_id,
|
||||
"status": execution.get("status", "running"),
|
||||
"input_data": data,
|
||||
},
|
||||
}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def duplicate_workflow(client: N8nClient, workflow_id: str, new_name: str) -> str:
|
||||
"""Duplicate a workflow"""
|
||||
try:
|
||||
# Get the original workflow
|
||||
original = await client.get_workflow(workflow_id)
|
||||
|
||||
# Create new workflow with same structure
|
||||
data = {
|
||||
"name": new_name,
|
||||
"nodes": original.get("nodes", []),
|
||||
"connections": original.get("connections", {}),
|
||||
"settings": original.get("settings", {}),
|
||||
}
|
||||
|
||||
new_workflow = await client.create_workflow(data)
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Workflow duplicated as '{new_name}'",
|
||||
"original": {"id": original.get("id"), "name": original.get("name")},
|
||||
"duplicate": {"id": new_workflow.get("id"), "name": new_workflow.get("name")},
|
||||
}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def export_workflow(client: N8nClient, workflow_id: str) -> str:
|
||||
"""Export workflow as JSON"""
|
||||
try:
|
||||
workflow = await client.get_workflow(workflow_id)
|
||||
|
||||
# Return full workflow JSON for export
|
||||
result = {"success": True, "workflow_json": workflow}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def import_workflow(
|
||||
client: N8nClient, workflow_json: dict[str, Any], name_override: str | None = None
|
||||
) -> str:
|
||||
"""Import workflow from JSON"""
|
||||
try:
|
||||
# Use name override if provided
|
||||
if name_override:
|
||||
workflow_json["name"] = name_override
|
||||
|
||||
# Ensure required fields
|
||||
if "nodes" not in workflow_json:
|
||||
workflow_json["nodes"] = []
|
||||
if "connections" not in workflow_json:
|
||||
workflow_json["connections"] = {}
|
||||
|
||||
workflow = await client.create_workflow(workflow_json)
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Workflow '{workflow.get('name')}' imported successfully",
|
||||
"workflow": {
|
||||
"id": workflow.get("id"),
|
||||
"name": workflow.get("name"),
|
||||
"active": workflow.get("active", False),
|
||||
},
|
||||
}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def get_workflow_tags(client: N8nClient, workflow_id: str) -> str:
|
||||
"""Get tags assigned to a workflow"""
|
||||
try:
|
||||
tags = await client.get_workflow_tags(workflow_id)
|
||||
|
||||
result = {"success": True, "workflow_id": workflow_id, "tags": tags}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
|
||||
async def set_workflow_tags(
|
||||
client: N8nClient, workflow_id: str, tag_ids: list[str] | None = None
|
||||
) -> str:
|
||||
"""Set tags for a workflow"""
|
||||
try:
|
||||
# Use empty list if tag_ids is None
|
||||
tags_to_set = tag_ids if tag_ids is not None else []
|
||||
|
||||
# Get current workflow
|
||||
current = await client.get_workflow(workflow_id)
|
||||
|
||||
# Update with new tags
|
||||
data = {
|
||||
"name": current.get("name"),
|
||||
"nodes": current.get("nodes", []),
|
||||
"connections": current.get("connections", {}),
|
||||
"tags": [{"id": tid} for tid in tags_to_set],
|
||||
}
|
||||
|
||||
workflow = await client.update_workflow(workflow_id, data)
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Tags updated for workflow {workflow_id}",
|
||||
"workflow_id": workflow_id,
|
||||
"tags": [t.get("name") for t in workflow.get("tags", [])],
|
||||
}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
||||
Reference in New Issue
Block a user