From 89c42420344dd14dd4b70834caaca85f6e8cbb3c Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Tue, 3 Feb 2026 12:35:22 -0500 Subject: [PATCH] Fix string escaping issues in generate-cli - Use single-quoted docstrings to avoid triple-quote escaping issues - Escape quotes in app_name derived from server_name - Add tests for descriptions with quotes and server names with quotes Addresses CodeRabbit review comments about insufficient escaping. --- src/fastmcp/cli/generate.py | 12 +++++++----- tests/cli/test_generate_cli.py | 31 ++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/fastmcp/cli/generate.py b/src/fastmcp/cli/generate.py index fa46f9a3b..49f8ad2d9 100644 --- a/src/fastmcp/cli/generate.py +++ b/src/fastmcp/cli/generate.py @@ -133,8 +133,8 @@ def _tool_function_source(tool: mcp.types.Tool) -> str: # Function name: sanitize to valid Python identifier fn_name = _to_python_identifier(tool.name) - # Docstring - description = (tool.description or "").replace('"""', '\\"\\"\\"') + # Docstring - use single-quoted docstrings to avoid triple-quote escaping issues + description = (tool.description or "").replace("\\", "\\\\").replace("'", "\\'") lines = [] lines.append("") @@ -148,7 +148,7 @@ def _tool_function_source(tool: mcp.types.Tool) -> str: lines.extend(param_lines) lines.append(") -> None:") - lines.append(f' """{description}"""') + lines.append(f" '''{description}'''") dict_items = ", ".join(call_args) lines.append(f" await _call_tool({tool.name!r}, {{{dict_items}}})") lines.append("") @@ -170,8 +170,10 @@ def generate_cli_script( ) -> str: """Generate the full CLI script source code.""" - # Determine app name from server_name - app_name = server_name.replace(" ", "-").lower() + # Determine app name from server_name - sanitize for use in string literal + app_name = ( + server_name.replace(" ", "-").lower().replace("\\", "\\\\").replace('"', '\\"') + ) # --- Header --- lines: list[str] = [] diff --git a/tests/cli/test_generate_cli.py b/tests/cli/test_generate_cli.py index 98f147a89..dfae49a08 100644 --- a/tests/cli/test_generate_cli.py +++ b/tests/cli/test_generate_cli.py @@ -230,7 +230,22 @@ class TestToolFunctionSource: }, ) source = _tool_function_source(tool) - assert '"""Say hello to someone."""' in source + assert "'''Say hello to someone.'''" in source + + def test_description_with_quotes(self): + tool = mcp.types.Tool( + name="fetch", + description="Fetch data from 'source' API.", + inputSchema={ + "properties": {"url": {"type": "string"}}, + "required": ["url"], + }, + ) + source = _tool_function_source(tool) + # Should escape single quotes in the description + assert r"Fetch data from \'source\' API." in source + # Generated code should compile + compile(source, "", "exec") # --------------------------------------------------------------------------- @@ -343,6 +358,20 @@ class TestGenerateCliScript: compile(script, "", "exec") assert "call_tool_app" in script + def test_server_name_with_quotes(self): + """Test that server names with quotes are properly escaped.""" + script = generate_cli_script( + server_name='Test "Server" Name', + server_spec="test", + transport_code='"http://localhost"', + extra_imports=set(), + tools=[], + ) + # Should compile without syntax errors + compile(script, "", "exec") + # App name should have escaped quotes + assert r'app = cyclopts.App(name="test-\"server\"-name"' in script + def test_compiles_with_unusual_names(self): tools = [ mcp.types.Tool(