invoke_local.py•3.26 kB
#!/usr/bin/env python3
"""Run a server tool locally (in-process) and print JSON.
Usage:
python invoke_local.py <tool_name> [--params '{"k":"v"}']
Examples:
python invoke_local.py list_nodes
python invoke_local.py get_node_topologies
"""
from __future__ import annotations
import argparse
import inspect
import json
import sys
from typing import Any, Callable
import server as server_mod
def _discover_tools(module) -> dict[str, Any]:
tools: dict[str, Any] = {}
for name in dir(module):
if name.startswith("_"):
continue
obj = getattr(module, name)
# Prefer plain functions decorated with @server.tool()
if inspect.isfunction(obj):
tools[name] = obj
continue
# Support FastMCP FunctionTool-like objects (have a callable `.fn`)
fn = getattr(obj, "fn", None)
if callable(fn):
tools[name] = obj # keep the wrapper; we'll unwrap in _call
return tools
def _call(func_or_wrapper: Any, params: dict[str, Any] | None) -> Any:
# Unwrap FunctionTool-like wrapper to the underlying function if present
if hasattr(func_or_wrapper, "fn") and callable(getattr(func_or_wrapper, "fn")):
func = getattr(func_or_wrapper, "fn")
else:
func = func_or_wrapper # assume it's a plain function
sig = inspect.signature(func)
if not params:
# If function takes no required params, just call it
if all(p.default is not inspect._empty or p.kind in (p.VAR_POSITIONAL, p.VAR_KEYWORD)
for p in sig.parameters.values()):
return func()
# Otherwise call without args anyway; let Python raise a clear error
return func()
# Try kwargs call with provided params
return func(**params)
def main(argv: list[str]) -> int:
parser = argparse.ArgumentParser(description="Run a server tool locally and print JSON output")
parser.add_argument("tool", help="Tool name to run (e.g. list_nodes, get_node_topologies)")
parser.add_argument("--params", help="Optional JSON object of parameters to pass to the tool", default=None)
args = parser.parse_args(argv)
tools = _discover_tools(server_mod)
if args.tool not in tools:
available = ", ".join(sorted(tools.keys())) or "<none>"
print(f"Unknown tool '{args.tool}'. Available: {available}", file=sys.stderr)
return 2
params_dict: dict[str, Any] | None = None
if args.params:
try:
loaded = json.loads(args.params)
if not isinstance(loaded, dict):
raise ValueError("--params must be a JSON object")
params_dict = loaded
except Exception as e:
print(f"Failed to parse --params as JSON object: {e}", file=sys.stderr)
return 2
try:
result = _call(tools[args.tool], params_dict)
except Exception as e:
print(f"Error running tool '{args.tool}': {e}", file=sys.stderr)
return 1
try:
print(json.dumps(result, indent=2, ensure_ascii=False, default=str))
except Exception:
# Fallback to str if not JSON-serializable
print(str(result))
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))