|
| 1 | +"""Rename server definition files to include transport tool suffix. |
| 2 | +Also updates the 'id' and 'name' fields inside each JSON file. |
| 3 | +Skips HTTP-only servers (they don't need a suffix since HTTP implies remote). |
| 4 | +Skips files that already have the correct suffix. |
| 5 | +""" |
| 6 | +import json, subprocess, sys, os |
| 7 | + |
| 8 | +# Files that should NOT get renamed (HTTP-only, no stdio variant, or special cases) |
| 9 | +SKIP_BRANCHES = set() |
| 10 | +# HTTP servers don't need transport suffix - they use the URL as transport |
| 11 | +HTTP_ONLY_FILES = { |
| 12 | + 'com.asana-mcp.json', |
| 13 | + 'com.clerk-mcp.json', |
| 14 | + 'com.cloudflare-observability.json', |
| 15 | + 'com.figma-mcp.json', |
| 16 | + 'com.honeycomb-mcp.json', |
| 17 | + 'io.intercom-mcp.json', |
| 18 | + 'io.sanity-mcp.json', |
| 19 | + 'com.stytch-mcp.json', |
| 20 | + 'com.vercel-mcp.json', |
| 21 | + 'town.val-mcp.json', |
| 22 | + # These already have proper suffixes |
| 23 | + 'com.apify-mcp-http.json', |
| 24 | + 'com.linear-mcp-http.json', |
| 25 | + 'com.square-mcp-http.json', |
| 26 | + 'com.vercel-mcp-http.json', |
| 27 | + 'com.mongodb-mcp-npx.json', |
| 28 | + 'com.mongodb-mcp-docker.json', |
| 29 | +} |
| 30 | + |
| 31 | +# Special: snyk uses 'snyk' command not npx/uvx/docker |
| 32 | +SPECIAL_TOOLS = { |
| 33 | + 'com.snyk-mcp.json': 'cli', # snyk cli, not a standard transport |
| 34 | +} |
| 35 | + |
| 36 | +def get_tool_suffix(transport): |
| 37 | + """Determine the suffix based on transport command.""" |
| 38 | + if transport.get('type') == 'http': |
| 39 | + return 'http' |
| 40 | + cmd = transport.get('command', '') |
| 41 | + if cmd in ('npx', 'uvx', 'docker'): |
| 42 | + return cmd |
| 43 | + if cmd == 'snyk': |
| 44 | + return 'cli' |
| 45 | + return cmd if cmd else None |
| 46 | + |
| 47 | +def run(args, **kwargs): |
| 48 | + return subprocess.run(args, capture_output=True, text=True, **kwargs) |
| 49 | + |
| 50 | +def main(): |
| 51 | + os.chdir('d:\\mcpmux\\mcp-servers') |
| 52 | + |
| 53 | + # Get main files to skip |
| 54 | + main_files = set(run(['git', 'ls-tree', '-r', '--name-only', 'main', '--', 'servers/']).stdout.strip().split('\n')) |
| 55 | + |
| 56 | + branches = run(['git', 'branch', '--list', 'fix/*']).stdout.strip().split('\n') |
| 57 | + branches = [b.strip().lstrip('* ') for b in branches if b.strip()] |
| 58 | + |
| 59 | + results = [] |
| 60 | + |
| 61 | + for branch in sorted(branches): |
| 62 | + if branch in SKIP_BRANCHES: |
| 63 | + continue |
| 64 | + |
| 65 | + # Get new files in this branch |
| 66 | + files_out = run(['git', 'ls-tree', '-r', '--name-only', branch, '--', 'servers/']) |
| 67 | + if files_out.returncode != 0: |
| 68 | + continue |
| 69 | + all_files = files_out.stdout.strip().split('\n') |
| 70 | + new_files = [f for f in all_files if f not in main_files and f.endswith('.json')] |
| 71 | + |
| 72 | + if not new_files: |
| 73 | + continue |
| 74 | + |
| 75 | + # Check out branch |
| 76 | + co = run(['git', 'checkout', branch]) |
| 77 | + if co.returncode != 0: |
| 78 | + print(f"SKIP {branch}: checkout failed") |
| 79 | + continue |
| 80 | + |
| 81 | + changed = False |
| 82 | + renames = [] |
| 83 | + |
| 84 | + for f in new_files: |
| 85 | + basename = os.path.basename(f) |
| 86 | + |
| 87 | + if basename in HTTP_ONLY_FILES: |
| 88 | + continue |
| 89 | + |
| 90 | + # Check if already has suffix |
| 91 | + name_no_ext = basename.replace('.json', '') |
| 92 | + if any(name_no_ext.endswith(s) for s in ['-npx', '-uvx', '-docker', '-http', '-cli']): |
| 93 | + continue |
| 94 | + |
| 95 | + # Read and parse |
| 96 | + try: |
| 97 | + with open(f) as fh: |
| 98 | + defn = json.load(fh) |
| 99 | + except: |
| 100 | + print(f"SKIP {branch}/{basename}: can't parse") |
| 101 | + continue |
| 102 | + |
| 103 | + transport = defn.get('transport', {}) |
| 104 | + suffix = get_tool_suffix(transport) |
| 105 | + |
| 106 | + if not suffix: |
| 107 | + print(f"SKIP {branch}/{basename}: unknown tool") |
| 108 | + continue |
| 109 | + |
| 110 | + # For HTTP-only servers (no stdio), skip suffix |
| 111 | + if suffix == 'http' and basename not in HTTP_ONLY_FILES: |
| 112 | + # This is an HTTP file paired with a stdio variant - already has -http suffix check above |
| 113 | + continue |
| 114 | + |
| 115 | + # Compute new filename and id |
| 116 | + new_basename = name_no_ext + f'-{suffix}.json' |
| 117 | + new_path = f'servers/{new_basename}' |
| 118 | + new_id = defn['id'] + f'-{suffix}' |
| 119 | + |
| 120 | + # Update name to include tool |
| 121 | + old_name = defn.get('name', '') |
| 122 | + suffix_label = suffix.upper() if suffix == 'uvx' else suffix |
| 123 | + if suffix == 'npx': |
| 124 | + new_name = f"{old_name} (npx)" |
| 125 | + elif suffix == 'uvx': |
| 126 | + new_name = f"{old_name} (uvx)" |
| 127 | + elif suffix == 'docker': |
| 128 | + new_name = f"{old_name} (Docker)" |
| 129 | + elif suffix == 'cli': |
| 130 | + new_name = f"{old_name} (CLI)" |
| 131 | + else: |
| 132 | + new_name = old_name |
| 133 | + |
| 134 | + # Update definition |
| 135 | + defn['id'] = new_id |
| 136 | + defn['name'] = new_name |
| 137 | + |
| 138 | + with open(f, 'w') as fh: |
| 139 | + json.dump(defn, fh, indent=2) |
| 140 | + fh.write('\n') |
| 141 | + |
| 142 | + # Git mv |
| 143 | + mv = run(['git', 'mv', f, new_path]) |
| 144 | + if mv.returncode != 0: |
| 145 | + print(f"ERROR {branch}: git mv {f} -> {new_path} failed: {mv.stderr}") |
| 146 | + continue |
| 147 | + |
| 148 | + renames.append((basename, new_basename, new_id)) |
| 149 | + changed = True |
| 150 | + |
| 151 | + if changed: |
| 152 | + # Stage and commit |
| 153 | + run(['git', 'add', '-A']) |
| 154 | + msg = f"fix: rename server files with transport suffix\n\n" |
| 155 | + for old, new, nid in renames: |
| 156 | + msg += f"- {old} -> {new} (id: {nid})\n" |
| 157 | + |
| 158 | + commit = run(['git', 'commit', '-s', |
| 159 | + '--author=Mohammod Al Amin Ashik <maa.ashik00@gmail.com>', |
| 160 | + '-m', msg]) |
| 161 | + if commit.returncode != 0: |
| 162 | + print(f"ERROR {branch}: commit failed: {commit.stderr}") |
| 163 | + continue |
| 164 | + |
| 165 | + # Push |
| 166 | + push = run(['git', 'push', 'origin', branch]) |
| 167 | + if push.returncode != 0: |
| 168 | + print(f"ERROR {branch}: push failed: {push.stderr}") |
| 169 | + continue |
| 170 | + |
| 171 | + for old, new, nid in renames: |
| 172 | + print(f"OK {branch}: {old} -> {new}") |
| 173 | + results.append((branch, old, new)) |
| 174 | + else: |
| 175 | + # Nothing to rename in this branch |
| 176 | + pass |
| 177 | + |
| 178 | + # Switch back to main |
| 179 | + run(['git', 'checkout', 'main']) |
| 180 | + |
| 181 | + print(f"\n=== SUMMARY ===") |
| 182 | + print(f"Renamed {len(results)} files across {len(set(r[0] for r in results))} branches") |
| 183 | + |
| 184 | +if __name__ == '__main__': |
| 185 | + main() |
0 commit comments