mirror of
https://github.com/invariantlabs-ai/invariant-gateway.git
synced 2026-02-12 14:32:45 +00:00
251 lines
7.1 KiB
Python
251 lines
7.1 KiB
Python
"""Script is used to run actions using the Invariant Gateway."""
|
|
|
|
import asyncio
|
|
import os
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
from typing import Optional
|
|
|
|
from gateway.mcp import mcp
|
|
|
|
|
|
LOCAL_COMPOSE_FILE = "gateway/docker-compose.local.yml"
|
|
|
|
|
|
# Handle signals to ensure clean shutdown
|
|
def signal_handler(sig, frame):
|
|
"""Handle signals for graceful shutdown."""
|
|
sys.exit(0)
|
|
|
|
|
|
def print_help():
|
|
"""Prints the help message."""
|
|
actions = {
|
|
"mcp": """
|
|
Runs the Invariant Gateway against MCP (Model Context Protocol) stdio servers with guardrailing and push to Explorer features.
|
|
""",
|
|
"server": """
|
|
Runs the Invariant Gateway server locally providing guardrailing and push to Explorer features.
|
|
Should be called with one of the following subcommands: build, up, down, logs.
|
|
A guardrails file can be passed with the flag: --guardrails-file=/path/to/guardrails/file.
|
|
""",
|
|
"help": "Shows this help message.",
|
|
}
|
|
|
|
for verb, description in actions.items():
|
|
print(f"{verb}: {description}")
|
|
|
|
|
|
def ensure_network_exists(network_name: str = "invariant-explorer-web") -> bool:
|
|
"""Ensure the Docker network exists."""
|
|
try:
|
|
# Check if network exists
|
|
result = subprocess.run(
|
|
["docker", "network", "inspect", network_name],
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
check=False,
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
print(f"Creating Docker network: {network_name}")
|
|
subprocess.run(
|
|
["docker", "network", "create", network_name],
|
|
check=True,
|
|
)
|
|
|
|
return True
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"Error creating Docker network: {e}")
|
|
return False
|
|
|
|
|
|
def setup_guardrails(guardrails_file_path: Optional[str] = None) -> bool:
|
|
"""Configure guardrails if specified."""
|
|
if not guardrails_file_path:
|
|
return True
|
|
|
|
if not os.path.isfile(guardrails_file_path):
|
|
print(
|
|
f"Error: Specified guardrails file does not exist: {guardrails_file_path}"
|
|
)
|
|
return False
|
|
|
|
# Convert to absolute path
|
|
guardrails_file_path = os.path.realpath(guardrails_file_path)
|
|
os.environ["GUARDRAILS_FILE_PATH"] = guardrails_file_path
|
|
|
|
# Check if INVARIANT_API_KEY is set
|
|
if not os.environ.get("INVARIANT_API_KEY"):
|
|
print(
|
|
"Error: A guardrails file is specified, but INVARIANT_API_KEY env var is not set. "
|
|
"This is required to validate guardrails."
|
|
)
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def build():
|
|
"""Build Docker containers using docker-compose."""
|
|
try:
|
|
print(f"Building using docker-compose file: {LOCAL_COMPOSE_FILE}")
|
|
subprocess.run(
|
|
["docker", "compose", "-f", str(LOCAL_COMPOSE_FILE), "build"],
|
|
check=True,
|
|
)
|
|
print("Build completed successfully")
|
|
return True
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"Error building containers: {e}")
|
|
return False
|
|
|
|
|
|
def up(guardrails_file_path: Optional[str] = None):
|
|
"""Set up the local server for the Invariant Gateway."""
|
|
# Ensure network exists
|
|
if not ensure_network_exists():
|
|
return 1
|
|
|
|
# Set up guardrails
|
|
if not setup_guardrails(guardrails_file_path=guardrails_file_path):
|
|
return 1
|
|
|
|
# Run the server
|
|
try:
|
|
# Start containers
|
|
print(f"Starting containers using docker-compose file: {LOCAL_COMPOSE_FILE}")
|
|
subprocess.run(
|
|
["docker", "compose", "-f", str(LOCAL_COMPOSE_FILE), "up", "-d"],
|
|
check=True,
|
|
)
|
|
|
|
# Wait for containers to start
|
|
time.sleep(2)
|
|
|
|
# Check if gateway container is running
|
|
result = subprocess.run(
|
|
["docker", "ps", "-qf", "name=invariant-gateway"],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
|
|
if not result.stdout.strip():
|
|
print("The invariant-gateway container failed to start.")
|
|
logs = subprocess.run(
|
|
["docker", "logs", "invariant-gateway"],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
print("Last 20 lines of logs:")
|
|
print("\n".join(logs.stdout.strip().split("\n")[-20:]))
|
|
return False
|
|
|
|
print("Gateway started at http://localhost:8005/api/v1/gateway/")
|
|
print("See http://localhost:8005/api/v1/gateway/docs for API documentation")
|
|
|
|
if guardrails_file_path:
|
|
print(f"Using Guardrails File: {guardrails_file_path}")
|
|
|
|
return True
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"Error starting containers: {e}")
|
|
return False
|
|
|
|
|
|
def down():
|
|
"""Stop the Docker containers."""
|
|
try:
|
|
print(f"Stopping containers using docker-compose file: {LOCAL_COMPOSE_FILE}")
|
|
subprocess.run(
|
|
["docker", "compose", "-f", str(LOCAL_COMPOSE_FILE), "down"],
|
|
check=True,
|
|
)
|
|
print("Containers stopped successfully")
|
|
return True
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"Error stopping containers: {e}")
|
|
return False
|
|
|
|
|
|
def logs():
|
|
"""Show container logs."""
|
|
try:
|
|
subprocess.run(
|
|
["docker", "compose", "-f", str(LOCAL_COMPOSE_FILE), "logs", "-f"],
|
|
check=True,
|
|
)
|
|
return True
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"Error showing logs: {e}")
|
|
return False
|
|
except KeyboardInterrupt:
|
|
print("\nExiting logs view")
|
|
return True
|
|
|
|
|
|
def run_server_command(command, args=None):
|
|
"""Run a server command."""
|
|
if args is None:
|
|
args = []
|
|
|
|
if command == "build":
|
|
return build()
|
|
elif command == "up":
|
|
# Parse guardrails file from args
|
|
guardrails_file = None
|
|
for arg in args:
|
|
if arg.startswith("--guardrails-file="):
|
|
guardrails_file = arg.split("=", 1)[1]
|
|
|
|
return up(guardrails_file)
|
|
elif command == "down":
|
|
return down()
|
|
elif command == "logs":
|
|
return logs()
|
|
else:
|
|
print(f"Unknown server command: {command}")
|
|
print("Available commands: build, up, down, logs")
|
|
return False
|
|
|
|
|
|
def main():
|
|
"""Entry point for the Invariant Gateway."""
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
|
|
if len(sys.argv) < 2:
|
|
print_help()
|
|
sys.exit(1)
|
|
|
|
verb = sys.argv[1]
|
|
if verb == "mcp":
|
|
return asyncio.run(mcp.execute(sys.argv[2:]))
|
|
if verb == "server":
|
|
if len(sys.argv) < 3:
|
|
print(
|
|
"Error: Missing command for server. Should be one of: build, up, down, logs"
|
|
)
|
|
print_help()
|
|
return 1
|
|
|
|
command = sys.argv[2]
|
|
args = sys.argv[3:]
|
|
if not run_server_command(command, args):
|
|
return 1
|
|
return 0
|
|
if verb == "help":
|
|
print_help()
|
|
return 0
|
|
print(f"[gateway/__main__.py] Unknown action: {verb}")
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|