Files
ITWorx_MCP_Hub-Public/services/runtime/connector_unraid.py
T

137 lines
5.1 KiB
Python

from __future__ import annotations
import os
from pathlib import Path
from typing import Any
from mcp.server.mcpserver import MCPServer
from mcp.server.mcpserver.context import Context
from connectors.unraid import RegisteredContainer, UnraidConnector
from connectors.unraid.client import FileBrokerTokenProvider, HttpBrokerClient
from packages.connector_kit import ConnectorContext, PageRequest
from .common import (
connector_context,
deterministic_id,
load_connector_identity_key,
read_regular_json,
request_headers,
)
from .mcp_server import add_operational_routes, add_workflow_prompts, run_mcp
def build_connector() -> tuple[UnraidConnector, set[str]]:
path = Path(os.environ.get("UNRAID_REGISTRATIONS_FILE", "/etc/itworx/registered-targets.json"))
document = read_regular_json(path)
registrations: list[RegisteredContainer] = []
projects: set[str] = set()
for item in document.get("containers", []):
if not isinstance(item, dict):
raise ValueError("Unraid registration contains an invalid entry")
project = str(item.get("project", "")).strip().lower()
environment = str(item.get("environment", "")).strip().lower()
registered_id = str(item.get("registeredId", "")).strip()
registrations.append(
RegisteredContainer(
registered_id,
deterministic_id("project", project),
environment,
)
)
projects.add(project)
if not projects:
raise ValueError("at least one Unraid project registration is required")
client = HttpBrokerClient(
os.environ.get("BROKER_URL", "http://docker-read-broker:8080"),
FileBrokerTokenProvider(
os.environ.get("BROKER_TOKEN_FILE", "/run/secrets/docker_broker_service_token")
),
)
return UnraidConnector(client, tuple(registrations)), projects
def build_server() -> MCPServer[Any]:
connector, projects = build_connector()
signing_key = load_connector_identity_key()
server: MCPServer[Any] = MCPServer(
"itworx-unraid",
title="ITWorx Unraid read-only connector",
version="1.0.0",
instructions=(
"Only broker-registered, bounded read operations are available. Inspect health "
"before requesting logs, keep log windows small, distinguish observed facts from "
"hypotheses, and never claim or request a container mutation."
),
)
add_operational_routes(server, "unraid-connector")
add_workflow_prompts(server, "unraid")
def current(context: Context[Any, Any]) -> ConnectorContext:
return connector_context(request_headers(context), projects, signing_key=signing_key)
@server.tool(name="unraid.host.status", structured_output=True)
async def host_status(context: Context[Any, Any]) -> dict[str, Any]:
return connector.host_status(current(context))
@server.tool(name="unraid.project.summary", structured_output=True)
async def project_summary(context: Context[Any, Any]) -> dict[str, Any]:
return connector.project_summary(current(context))
@server.tool(name="unraid.storage.list", structured_output=True)
async def storage_list(
context: Context[Any, Any], limit: int = 25, cursor: str | None = None
) -> dict[str, Any]:
return connector.storage_list(
current(context), page=PageRequest(limit=limit, cursor=cursor)
)
@server.tool(name="unraid.container.list", structured_output=True)
async def container_list(
context: Context[Any, Any],
state: str | None = None,
limit: int = 25,
cursor: str | None = None,
) -> dict[str, Any]:
return connector.container_list(
current(context), state=state, page=PageRequest(limit=limit, cursor=cursor)
)
@server.tool(name="unraid.container.inspect", structured_output=True)
async def container_inspect(containerRef: str, context: Context[Any, Any]) -> dict[str, Any]:
return connector.container_inspect(current(context), containerRef)
@server.tool(name="unraid.container.health", structured_output=True)
async def container_health(containerRef: str, context: Context[Any, Any]) -> dict[str, Any]:
return connector.container_health(current(context), containerRef)
@server.tool(name="unraid.container.logs", structured_output=True)
async def container_logs(
containerRef: str,
context: Context[Any, Any],
tail: int = 200,
sinceSeconds: int | None = None,
contains: str | None = None,
) -> dict[str, Any]:
return connector.container_logs(
current(context),
containerRef,
tail=tail,
since_seconds=sinceSeconds,
contains=contains,
)
@server.tool(name="unraid.network.list", structured_output=True)
async def network_list(
context: Context[Any, Any], limit: int = 25, cursor: str | None = None
) -> dict[str, Any]:
return connector.network_list(
current(context), page=PageRequest(limit=limit, cursor=cursor)
)
return server
def run() -> None:
run_mcp(build_server())