Created
August 7, 2025 08:13
-
-
Save kierr/fa012d8c437fac80cab8fafb8b817ff4 to your computer and use it in GitHub Desktop.
Always Allow All (Kilo Code MCP Servers)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| always_allow_all.py [OPTIONS] CONFIG.json | |
| Kilo Code currently has no way to always allow all tools for an MCP server. | |
| This script will always allow every tool for every server in a provided configuration file. | |
| Options: | |
| -v, --verbose Increase output verbosity | |
| -r, --retries Number of retries for flaky servers (default: 1) | |
| -h, --help Show this help message | |
| """ | |
| from __future__ import annotations | |
| import json, os, pathlib, subprocess, sys, textwrap, time, typing as t, itertools, argparse | |
| try: | |
| import requests # pip install requests if missing | |
| except ModuleNotFoundError: | |
| sys.exit("❌ Please `pip install requests` first") | |
| # ───────────────────────── helpers ───────────────────────── # | |
| JSON = t.Dict[str, t.Any] | |
| RPC_REQ = {"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}} | |
| def tools_from_http(url: str, quiet: bool = True) -> list[str]: | |
| """POST the tools/list request to an HTTP-exposed MCP server.""" | |
| if not quiet: | |
| print(f"📡 Connecting to HTTP server: {url}") | |
| try: | |
| r = requests.post(url, json=RPC_REQ, timeout=15) | |
| r.raise_for_status() | |
| result = r.json()["result"] | |
| names = [t["name"] for t in result["tools"]] | |
| # Handle pagination if present | |
| while result.get("nextCursor"): | |
| rpc = RPC_REQ | {"params": {"cursor": result["nextCursor"]}} | |
| r = requests.post(url, json=rpc, timeout=15) | |
| r.raise_for_status() | |
| result = r.json()["result"] | |
| names.extend(t["name"] for t in result["tools"]) | |
| return names | |
| except requests.exceptions.RequestException as e: | |
| raise Exception(f"HTTP connection failed: {e}") | |
| def tools_from_stdio(command: str, args: list[str], env: JSON, quiet: bool = True) -> list[str]: | |
| """Spawn the server (docker run / npx … ) and speak JSON-RPC over stdio.""" | |
| if not quiet: | |
| cmd_display = f"{command} {' '.join(args)}" | |
| print(f"🚀 Spawning server: {cmd_display}") | |
| # Suppress server logs by redirecting stderr | |
| stderr_redirect = subprocess.DEVNULL if quiet else None | |
| proc = subprocess.Popen( | |
| [command, *args], | |
| stdin=subprocess.PIPE, | |
| stdout=subprocess.PIPE, | |
| stderr=stderr_redirect, | |
| text=True, | |
| env={**os.environ, **env}, | |
| ) | |
| try: | |
| proc.stdin.write(json.dumps(RPC_REQ) + "\n") | |
| proc.stdin.flush() | |
| raw = proc.stdout.readline().strip() | |
| result = json.loads(raw)["result"] | |
| names = [t["name"] for t in result["tools"]] | |
| while result.get("nextCursor"): | |
| rpc = RPC_REQ | {"params": {"cursor": result["nextCursor"]}} | |
| proc.stdin.write(json.dumps(rpc) + "\n") | |
| proc.stdin.flush() | |
| raw = proc.stdout.readline().strip() | |
| result = json.loads(raw)["result"] | |
| names.extend(t["name"] for t in result["tools"]) | |
| return names | |
| finally: | |
| proc.terminate() | |
| try: proc.wait(timeout=2) | |
| except subprocess.TimeoutExpired: | |
| proc.kill() | |
| def enumerate_tools(entry: JSON, quiet: bool = True) -> list[str]: | |
| """Choose the correct transport automatically.""" | |
| if "url" in entry: # simplest case | |
| return tools_from_http(entry["url"], quiet) | |
| if entry.get("command") and entry.get("args"): | |
| return tools_from_stdio(entry["command"], entry["args"], entry.get("env", {}), quiet) | |
| raise ValueError("Cannot determine how to talk to server entry") | |
| # ───────────────────────── main flow ───────────────────────── # | |
| def main(): | |
| parser = argparse.ArgumentParser( | |
| description="Always allow all tools for MCP servers in a configuration file", | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| epilog=__doc__ | |
| ) | |
| parser.add_argument("config", help="Path to the MCP configuration JSON file") | |
| parser.add_argument("-v", "--verbose", action="store_true", help="Increase output verbosity") | |
| parser.add_argument("-r", "--retries", type=int, default=1, help="Number of retries for flaky servers (default: 1)") | |
| try: | |
| args = parser.parse_args() | |
| except SystemExit: | |
| # argparse calls sys.exit() on -h/--help, but we want to show our custom help | |
| sys.exit(0) | |
| path = pathlib.Path(args.config).expanduser().resolve() | |
| if not path.exists(): | |
| sys.exit(f"❌ Configuration file not found: {path}") | |
| try: | |
| cfg: JSON = json.loads(path.read_text()) | |
| except json.JSONDecodeError as e: | |
| sys.exit(f"❌ Invalid JSON in configuration file: {e}") | |
| if args.verbose: | |
| print(f"📁 Loading configuration from: {path}") | |
| # Collect statistics | |
| stats = { | |
| "total_servers": 0, | |
| "processed_servers": 0, | |
| "successful_servers": 0, | |
| "failed_servers": [], | |
| "disabled_servers": [], | |
| "total_tools_added": 0, | |
| "total_tools_existing": 0, | |
| "server_details": {} | |
| } | |
| mcp_servers = cfg.get("mcpServers", {}) | |
| stats["total_servers"] = len(mcp_servers) | |
| if args.verbose: | |
| print(f"🔧 Found {stats['total_servers']} MCP servers") | |
| for name, entry in mcp_servers.items(): | |
| if entry.get("disabled", False): | |
| stats["disabled_servers"].append(name) | |
| if args.verbose: | |
| print(f"⏭️ {name}: skipped (disabled)") | |
| continue | |
| stats["processed_servers"] += 1 | |
| success = False | |
| last_error = None | |
| tools = [] | |
| # Try multiple times for flaky servers | |
| for attempt in range(args.retries): | |
| try: | |
| if args.retries > 1 and args.verbose: | |
| print(f"🔄 {name}: attempt {attempt + 1}/{args.retries}") | |
| tools = enumerate_tools(entry, not args.verbose) | |
| success = True | |
| break | |
| except Exception as e: | |
| last_error = e | |
| if attempt < args.retries - 1: # Not the last attempt | |
| if args.verbose: | |
| print(f"⚠️ {name}: attempt {attempt + 1} failed, retrying...") | |
| time.sleep(1) # Brief delay before retry | |
| if not success: | |
| stats["failed_servers"].append((name, str(last_error))) | |
| if args.verbose: | |
| print(f"❌ {name}: {last_error} – skipped") | |
| continue | |
| stats["successful_servers"] += 1 | |
| existing = set(entry.get("alwaysAllow", [])) | |
| merged = sorted(existing | set(tools)) | |
| # Store details for summary | |
| stats["server_details"][name] = { | |
| "existing_count": len(existing), | |
| "new_count": len(tools), | |
| "total_after_update": len(merged), | |
| "added_count": len(merged) - len(existing) | |
| } | |
| stats["total_tools_added"] += len(merged) - len(existing) | |
| stats["total_tools_existing"] += len(existing) | |
| if merged != entry.get("alwaysAllow"): | |
| entry["alwaysAllow"] = merged | |
| added = len(merged) - len(existing) | |
| if args.verbose: | |
| print(f"✅ {name}: added {added} tool(s), now {len(merged)} total") | |
| elif args.verbose: | |
| print(f"✅ {name}: already up to date ({len(merged)} tools)") | |
| # Print summary | |
| print(f"📊 Summary:") | |
| print(f" Total servers found: {stats['total_servers']}") | |
| print(f" Processed servers: {stats['processed_servers']}") | |
| print(f" Successful servers: {stats['successful_servers']}") | |
| print(f" Disabled servers: {len(stats['disabled_servers'])}") | |
| print(f" Failed servers: {len(stats['failed_servers'])}") | |
| if stats["disabled_servers"]: | |
| print(f" Disabled: {', '.join(stats['disabled_servers'])}") | |
| if stats["failed_servers"]: | |
| print(f" Failed:") | |
| for name, error in stats["failed_servers"]: | |
| print(f" - {name}: {error}") | |
| print(f" Tools added: {stats['total_tools_added']}") | |
| print(f" Tools already present: {stats['total_tools_existing']}") | |
| # Print per-server details | |
| if stats["server_details"]: | |
| print(f" Server details:") | |
| for name, details in stats["server_details"].items(): | |
| if details["added_count"] > 0: | |
| print(f" - {name}: {details['existing_count']} existing, {details['added_count']} added, {details['total_after_update']} total") | |
| else: | |
| print(f" - {name}: {details['existing_count']} existing, up to date") | |
| if stats["total_tools_added"] > 0: | |
| path.write_text(json.dumps(cfg, indent=2) + "\n") | |
| print(f"✅ Configuration updated with {stats['total_tools_added']} new tools") | |
| else: | |
| print("✅ No changes needed - all tools already allowed") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment