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.
This commit is contained in:
Jeremiah Lowin 2026-02-03 12:35:22 -05:00
commit 89c4242034
No known key found for this signature in database
2 changed files with 37 additions and 6 deletions

View file

@ -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] = []

View file

@ -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, "<test>", "exec")
# ---------------------------------------------------------------------------
@ -343,6 +358,20 @@ class TestGenerateCliScript:
compile(script, "<generated>", "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, "<generated>", "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(