feat(dx): add fastmcp.json configs, module-level tool registration, tool timeout

- Add fastmcp.http.json and fastmcp.stdio.json declarative server configs
  for streamable-http (:6970) and stdio transports respectively
- Move register_all_modules() to module level in server.py so
  `fastmcp run server.py --reload` discovers the fully-wired mcp object
  without going through run_server() — tools registered exactly once
- Add timeout=120 to @mcp.tool() decorator as a global safety net;
  any hung subaction returns a clean MCP error instead of hanging forever
- Document fastmcp run --reload, fastmcp list, fastmcp call in README
- Bump version 1.0.1 → 1.1.0

Co-authored-by: Claude <claude@anthropic.com>
This commit is contained in:
Jacob Magar
2026-03-16 10:32:16 -04:00
parent 5187cf730f
commit f69aa94826
8 changed files with 76 additions and 7 deletions

View File

@@ -1,7 +1,7 @@
{ {
"name": "unraid", "name": "unraid",
"description": "Query and monitor Unraid servers via GraphQL API - array status, disk health, containers, VMs, system monitoring", "description": "Query and monitor Unraid servers via GraphQL API - array status, disk health, containers, VMs, system monitoring",
"version": "1.0.1", "version": "1.1.0",
"author": { "author": {
"name": "jmagar", "name": "jmagar",
"email": "jmagar@users.noreply.github.com" "email": "jmagar@users.noreply.github.com"

View File

@@ -399,6 +399,28 @@ uv run unraid-mcp-server
# Or run via module directly # Or run via module directly
uv run -m unraid_mcp.main uv run -m unraid_mcp.main
# Hot-reload dev server (restarts on file changes)
fastmcp run fastmcp.http.json --reload
# Run via named config files
fastmcp run fastmcp.http.json # streamable-http on :6970
fastmcp run fastmcp.stdio.json # stdio transport
```
### Ad-hoc Tool Testing (fastmcp CLI)
```bash
# Introspect the running server
fastmcp list http://localhost:6970/mcp
fastmcp list http://localhost:6970/mcp --input-schema
# Call a tool directly (HTTP)
fastmcp call http://localhost:6970/mcp unraid action=health subaction=check
fastmcp call http://localhost:6970/mcp unraid action=docker subaction=list
# Call without a running server (stdio config)
fastmcp list fastmcp.stdio.json
fastmcp call fastmcp.stdio.json unraid action=health subaction=check
``` ```
--- ---

23
fastmcp.http.json Normal file
View File

@@ -0,0 +1,23 @@
{
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
"source": {
"path": "unraid_mcp/server.py",
"entrypoint": "mcp"
},
"environment": {
"type": "uv",
"python": "3.12",
"editable": ["."]
},
"deployment": {
"transport": "http",
"host": "0.0.0.0",
"port": 6970,
"path": "/mcp",
"log_level": "INFO",
"env": {
"UNRAID_API_URL": "${UNRAID_API_URL}",
"UNRAID_API_KEY": "${UNRAID_API_KEY}"
}
}
}

20
fastmcp.stdio.json Normal file
View File

@@ -0,0 +1,20 @@
{
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
"source": {
"path": "unraid_mcp/server.py",
"entrypoint": "mcp"
},
"environment": {
"type": "uv",
"python": "3.12",
"editable": ["."]
},
"deployment": {
"transport": "stdio",
"log_level": "INFO",
"env": {
"UNRAID_API_URL": "${UNRAID_API_URL}",
"UNRAID_API_KEY": "${UNRAID_API_KEY}"
}
}
}

View File

@@ -10,7 +10,7 @@ build-backend = "hatchling.build"
# ============================================================================ # ============================================================================
[project] [project]
name = "unraid-mcp" name = "unraid-mcp"
version = "1.0.1" version = "1.1.0"
description = "MCP Server for Unraid API - provides tools to interact with an Unraid server's GraphQL API" description = "MCP Server for Unraid API - provides tools to interact with an Unraid server's GraphQL API"
readme = "README.md" readme = "README.md"
license = {file = "LICENSE"} license = {file = "LICENSE"}

View File

@@ -85,6 +85,10 @@ mcp = FastMCP(
# Note: SubscriptionManager singleton is defined in subscriptions/manager.py # Note: SubscriptionManager singleton is defined in subscriptions/manager.py
# and imported by resources.py - no duplicate instance needed here # and imported by resources.py - no duplicate instance needed here
# Register all modules at import time so `fastmcp run server.py --reload` can
# discover the fully-configured `mcp` object without going through run_server().
# run_server() no longer calls this — tools are registered exactly once here.
def register_all_modules() -> None: def register_all_modules() -> None:
"""Register all tools and resources with the MCP instance.""" """Register all tools and resources with the MCP instance."""
@@ -103,6 +107,9 @@ def register_all_modules() -> None:
raise raise
register_all_modules()
def run_server() -> None: def run_server() -> None:
"""Run the MCP server with the configured transport.""" """Run the MCP server with the configured transport."""
# Validate required configuration before anything else # Validate required configuration before anything else
@@ -125,9 +132,6 @@ def run_server() -> None:
"Only use this in trusted networks or for development." "Only use this in trusted networks or for development."
) )
# Register all modules
register_all_modules()
logger.info( logger.info(
f"Starting Unraid MCP Server on {UNRAID_MCP_HOST}:{UNRAID_MCP_PORT} using {UNRAID_MCP_TRANSPORT} transport..." f"Starting Unraid MCP Server on {UNRAID_MCP_HOST}:{UNRAID_MCP_PORT} using {UNRAID_MCP_TRANSPORT} transport..."
) )

View File

@@ -1722,7 +1722,7 @@ UNRAID_ACTIONS = Literal[
def register_unraid_tool(mcp: FastMCP) -> None: def register_unraid_tool(mcp: FastMCP) -> None:
"""Register the single `unraid` tool with the FastMCP instance.""" """Register the single `unraid` tool with the FastMCP instance."""
@mcp.tool() @mcp.tool(timeout=120)
async def unraid( async def unraid(
action: UNRAID_ACTIONS, action: UNRAID_ACTIONS,
subaction: str, subaction: str,

2
uv.lock generated
View File

@@ -1572,7 +1572,7 @@ wheels = [
[[package]] [[package]]
name = "unraid-mcp" name = "unraid-mcp"
version = "1.0.0" version = "1.0.1"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "fastapi" }, { name = "fastapi" },