| |
| from __future__ import annotations |
|
|
| import argparse |
| import re |
| from pathlib import Path |
|
|
|
|
| DEFAULT_MCP_ROOT = ( |
| Path(__file__).resolve().parent |
| / "biomni_web" |
| / "backend" |
| / "data" |
| / "mcp_generated" |
| ) |
|
|
|
|
| TOOL_DECORATOR_RE = re.compile(r"^(\s*)@mcp\.tool\(\)\s*$") |
| COMMENTED_TOOL_DECORATOR_RE = re.compile(r"^(\s*)#\s*@mcp\.tool\(\)\s*$") |
|
|
|
|
| def build_server_name(server_file: Path) -> str: |
| stem = server_file.stem |
| if stem.endswith("_server"): |
| stem = stem[: -len("_server")] |
| return f"local_{stem.replace('-', '_').replace('.', '_')}" |
|
|
|
|
| def patch_file(server_file: Path) -> bool: |
| text = server_file.read_text(encoding="utf-8") |
| original = text |
|
|
| if "_shim_server.py" in server_file.name: |
| return False |
|
|
| if "mcp = FastMCP(" in text and 'mcp.run(transport="stdio")' in text: |
| return False |
|
|
| lines = text.splitlines() |
|
|
| |
| lines = [COMMENTED_TOOL_DECORATOR_RE.sub(r"\1@mcp.tool()", line) for line in lines] |
|
|
| first_decorator_idx = None |
| for idx, line in enumerate(lines): |
| if TOOL_DECORATOR_RE.match(line): |
| first_decorator_idx = idx |
| break |
|
|
| if first_decorator_idx is None: |
| return False |
|
|
| if "from mcp.server.fastmcp import FastMCP" not in "\n".join(lines): |
| insertion = [ |
| "from mcp.server.fastmcp import FastMCP", |
| "", |
| f"SERVER_NAME = {build_server_name(server_file)!r}", |
| "mcp = FastMCP(SERVER_NAME)", |
| "", |
| ] |
| lines[first_decorator_idx:first_decorator_idx] = insertion |
|
|
| patched = "\n".join(lines).rstrip() + "\n" |
|
|
| if 'mcp.run(transport="stdio")' not in patched: |
| patched += '\nif __name__ == "__main__":\n mcp.run(transport="stdio")\n' |
|
|
| if patched != original: |
| server_file.write_text(patched, encoding="utf-8") |
| return True |
|
|
| return False |
|
|
|
|
| def main() -> None: |
| parser = argparse.ArgumentParser( |
| description="Patch raw MCP server files to be directly runnable with FastMCP." |
| ) |
| parser.add_argument( |
| "--mcp-root", |
| default=str(DEFAULT_MCP_ROOT), |
| help="Root directory containing mcp_* subdirectories.", |
| ) |
| args = parser.parse_args() |
|
|
| mcp_root = Path(args.mcp_root).resolve() |
| if not mcp_root.is_dir(): |
| raise SystemExit(f"MCP root does not exist or is not a directory: {mcp_root}") |
|
|
| changed = 0 |
| scanned = 0 |
| for server_file in sorted(mcp_root.glob("mcp_*/app/*_server.py")): |
| scanned += 1 |
| if patch_file(server_file): |
| changed += 1 |
|
|
| print(f"Scanned {scanned} server files.") |
| print(f"Patched {changed} raw MCP server files.") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|