feat(mcp): Add full functional parity for C/C++ tools
This commit is contained in:
@@ -77,6 +77,8 @@ MUTATING_TOOLS: frozenset[str] = frozenset({
|
||||
"py_set_signature",
|
||||
"py_set_var_declaration",
|
||||
"edit_file",
|
||||
"ts_c_update_definition",
|
||||
"ts_cpp_update_definition",
|
||||
})
|
||||
|
||||
# ------------------------------------------------------------------ state
|
||||
@@ -370,6 +372,98 @@ def ts_cpp_get_code_outline(path: str) -> str:
|
||||
except Exception as e:
|
||||
return f"ERROR generating outline for '{path}': {e}"
|
||||
|
||||
def ts_c_get_definition(path: str, name: str) -> str:
|
||||
"""Returns the source code for a specific definition in a C file."""
|
||||
p, err = _resolve_and_check(path)
|
||||
if err: return err
|
||||
assert p is not None
|
||||
if not p.exists(): return f"ERROR: file not found: {path}"
|
||||
try:
|
||||
from src.file_cache import ASTParser
|
||||
code = p.read_text(encoding="utf-8")
|
||||
parser = ASTParser("c")
|
||||
return parser.get_definition(code, name, path=str(p))
|
||||
except Exception as e:
|
||||
return f"ERROR retrieving definition '{name}' from '{path}': {e}"
|
||||
|
||||
def ts_cpp_get_definition(path: str, name: str) -> str:
|
||||
"""Returns the source code for a specific definition in a C++ file."""
|
||||
p, err = _resolve_and_check(path)
|
||||
if err: return err
|
||||
assert p is not None
|
||||
if not p.exists(): return f"ERROR: file not found: {path}"
|
||||
try:
|
||||
from src.file_cache import ASTParser
|
||||
code = p.read_text(encoding="utf-8")
|
||||
parser = ASTParser("cpp")
|
||||
return parser.get_definition(code, name, path=str(p))
|
||||
except Exception as e:
|
||||
return f"ERROR retrieving definition '{name}' from '{path}': {e}"
|
||||
|
||||
def ts_c_get_signature(path: str, name: str) -> str:
|
||||
"""Returns the signature part of a function in a C file."""
|
||||
p, err = _resolve_and_check(path)
|
||||
if err: return err
|
||||
assert p is not None
|
||||
if not p.exists(): return f"ERROR: file not found: {path}"
|
||||
try:
|
||||
from src.file_cache import ASTParser
|
||||
code = p.read_text(encoding="utf-8")
|
||||
parser = ASTParser("c")
|
||||
return parser.get_signature(code, name, path=str(p))
|
||||
except Exception as e:
|
||||
return f"ERROR retrieving signature '{name}' from '{path}': {e}"
|
||||
|
||||
def ts_cpp_get_signature(path: str, name: str) -> str:
|
||||
"""Returns the signature part of a function or method in a C++ file."""
|
||||
p, err = _resolve_and_check(path)
|
||||
if err: return err
|
||||
assert p is not None
|
||||
if not p.exists(): return f"ERROR: file not found: {path}"
|
||||
try:
|
||||
from src.file_cache import ASTParser
|
||||
code = p.read_text(encoding="utf-8")
|
||||
parser = ASTParser("cpp")
|
||||
return parser.get_signature(code, name, path=str(p))
|
||||
except Exception as e:
|
||||
return f"ERROR retrieving signature '{name}' from '{path}': {e}"
|
||||
|
||||
def ts_c_update_definition(path: str, name: str, new_content: str) -> str:
|
||||
"""Surgically replace the definition of a function in a C file."""
|
||||
p, err = _resolve_and_check(path)
|
||||
if err: return err
|
||||
assert p is not None
|
||||
if not p.exists(): return f"ERROR: file not found: {path}"
|
||||
try:
|
||||
from src.file_cache import ASTParser
|
||||
code = p.read_text(encoding="utf-8")
|
||||
parser = ASTParser("c")
|
||||
updated_code = parser.update_definition(code, name, new_content, path=str(p))
|
||||
if updated_code.startswith("ERROR:"):
|
||||
return updated_code
|
||||
p.write_text(updated_code, encoding="utf-8")
|
||||
return f"Successfully updated definition '{name}' in {path}"
|
||||
except Exception as e:
|
||||
return f"ERROR updating definition '{name}' in '{path}': {e}"
|
||||
|
||||
def ts_cpp_update_definition(path: str, name: str, new_content: str) -> str:
|
||||
"""Surgically replace the definition of a class or function in a C++ file."""
|
||||
p, err = _resolve_and_check(path)
|
||||
if err: return err
|
||||
assert p is not None
|
||||
if not p.exists(): return f"ERROR: file not found: {path}"
|
||||
try:
|
||||
from src.file_cache import ASTParser
|
||||
code = p.read_text(encoding="utf-8")
|
||||
parser = ASTParser("cpp")
|
||||
updated_code = parser.update_definition(code, name, new_content, path=str(p))
|
||||
if updated_code.startswith("ERROR:"):
|
||||
return updated_code
|
||||
p.write_text(updated_code, encoding="utf-8")
|
||||
return f"Successfully updated definition '{name}' in {path}"
|
||||
except Exception as e:
|
||||
return f"ERROR updating definition '{name}' in '{path}': {e}"
|
||||
|
||||
def get_file_slice(path: str, start_line: int, end_line: int) -> str:
|
||||
"""Return a specific line range from a file."""
|
||||
p, err = _resolve_and_check(path)
|
||||
@@ -1127,6 +1221,18 @@ def dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
|
||||
return ts_c_get_code_outline(path)
|
||||
if tool_name == "ts_cpp_get_code_outline":
|
||||
return ts_cpp_get_code_outline(path)
|
||||
if tool_name == "ts_c_get_definition":
|
||||
return ts_c_get_definition(path, str(tool_input.get("name", "")))
|
||||
if tool_name == "ts_cpp_get_definition":
|
||||
return ts_cpp_get_definition(path, str(tool_input.get("name", "")))
|
||||
if tool_name == "ts_c_get_signature":
|
||||
return ts_c_get_signature(path, str(tool_input.get("name", "")))
|
||||
if tool_name == "ts_cpp_get_signature":
|
||||
return ts_cpp_get_signature(path, str(tool_input.get("name", "")))
|
||||
if tool_name == "ts_c_update_definition":
|
||||
return ts_c_update_definition(path, str(tool_input.get("name", "")), str(tool_input.get("new_content", "")))
|
||||
if tool_name == "ts_cpp_update_definition":
|
||||
return ts_cpp_update_definition(path, str(tool_input.get("name", "")), str(tool_input.get("new_content", "")))
|
||||
if tool_name == "py_get_definition":
|
||||
return py_get_definition(path, str(tool_input.get("name", "")))
|
||||
if tool_name == "py_update_definition":
|
||||
@@ -1394,6 +1500,128 @@ MCP_TOOL_SPECS: list[dict[str, Any]] = [
|
||||
"required": ["path"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "ts_c_get_definition",
|
||||
"description": (
|
||||
"Get the full source code of a specific function or struct definition in a C file. "
|
||||
"This is more efficient than reading the whole file if you know what you're looking for."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Path to the C file.",
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the function or struct to retrieve.",
|
||||
}
|
||||
},
|
||||
"required": ["path", "name"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "ts_cpp_get_definition",
|
||||
"description": (
|
||||
"Get the full source code of a specific class, function, or method definition in a C++ file. "
|
||||
"This is more efficient than reading the whole file if you know what you're looking for."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Path to the C++ file.",
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the class or function to retrieve. Use 'ClassName::method_name' for methods.",
|
||||
}
|
||||
},
|
||||
"required": ["path", "name"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "ts_c_get_signature",
|
||||
"description": "Get only the signature part of a C function.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Path to the C file."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the function."
|
||||
}
|
||||
},
|
||||
"required": ["path", "name"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ts_cpp_get_signature",
|
||||
"description": "Get only the signature part of a C++ function or method.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Path to the C++ file."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the function/method (e.g. 'ClassName::method_name')."
|
||||
}
|
||||
},
|
||||
"required": ["path", "name"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ts_c_update_definition",
|
||||
"description": "Surgically replace the definition of a function in a C file using AST to find line ranges.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Path to the C file."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of function."
|
||||
},
|
||||
"new_content": {
|
||||
"type": "string",
|
||||
"description": "Complete new source for the definition."
|
||||
}
|
||||
},
|
||||
"required": ["path", "name", "new_content"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ts_cpp_update_definition",
|
||||
"description": "Surgically replace the definition of a class or function in a C++ file using AST to find line ranges.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Path to the C++ file."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of class/function/method."
|
||||
},
|
||||
"new_content": {
|
||||
"type": "string",
|
||||
"description": "Complete new source for the definition."
|
||||
}
|
||||
},
|
||||
"required": ["path", "name", "new_content"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_file_slice",
|
||||
"description": "Read a specific line range from a file. Useful for reading parts of very large files.",
|
||||
|
||||
@@ -86,3 +86,93 @@ def test_ts_cpp_get_code_outline_dispatch(tmp_path, mock_resolve):
|
||||
mock_instance.get_code_outline.assert_called_once()
|
||||
assert result and len(result) > 0
|
||||
assert "[Func] main (Lines 1-1)" in result
|
||||
|
||||
def test_ts_c_get_definition_dispatch(tmp_path, mock_resolve):
|
||||
# Verify ts_c_get_definition via dispatch
|
||||
c_file = tmp_path / "test.c"
|
||||
c_file.write_text("void foo() {}")
|
||||
|
||||
with patch("src.file_cache.ASTParser") as mock_parser_cls:
|
||||
mock_instance = mock_parser_cls.return_value
|
||||
mock_instance.get_definition.return_value = "void foo() {}"
|
||||
|
||||
result = dispatch("ts_c_get_definition", {"path": str(c_file), "name": "foo"})
|
||||
|
||||
mock_parser_cls.assert_called_once_with("c")
|
||||
mock_instance.get_definition.assert_called_once()
|
||||
assert "void foo() {}" in result
|
||||
|
||||
def test_ts_cpp_get_definition_dispatch(tmp_path, mock_resolve):
|
||||
# Verify ts_cpp_get_definition via dispatch
|
||||
cpp_file = tmp_path / "test.cpp"
|
||||
cpp_file.write_text("void foo() {}")
|
||||
|
||||
with patch("src.file_cache.ASTParser") as mock_parser_cls:
|
||||
mock_instance = mock_parser_cls.return_value
|
||||
mock_instance.get_definition.return_value = "void foo() {}"
|
||||
|
||||
result = dispatch("ts_cpp_get_definition", {"path": str(cpp_file), "name": "foo"})
|
||||
|
||||
mock_parser_cls.assert_called_once_with("cpp")
|
||||
mock_instance.get_definition.assert_called_once()
|
||||
assert "void foo() {}" in result
|
||||
|
||||
def test_ts_c_get_signature_dispatch(tmp_path, mock_resolve):
|
||||
# Verify ts_c_get_signature via dispatch
|
||||
c_file = tmp_path / "test.c"
|
||||
c_file.write_text("void foo() {}")
|
||||
|
||||
with patch("src.file_cache.ASTParser") as mock_parser_cls:
|
||||
mock_instance = mock_parser_cls.return_value
|
||||
mock_instance.get_signature.return_value = "void foo()"
|
||||
|
||||
result = dispatch("ts_c_get_signature", {"path": str(c_file), "name": "foo"})
|
||||
|
||||
mock_parser_cls.assert_called_once_with("c")
|
||||
mock_instance.get_signature.assert_called_once()
|
||||
assert "void foo()" in result
|
||||
|
||||
def test_ts_cpp_get_signature_dispatch(tmp_path, mock_resolve):
|
||||
# Verify ts_cpp_get_signature via dispatch
|
||||
cpp_file = tmp_path / "test.cpp"
|
||||
cpp_file.write_text("void foo() {}")
|
||||
|
||||
with patch("src.file_cache.ASTParser") as mock_parser_cls:
|
||||
mock_instance = mock_parser_cls.return_value
|
||||
mock_instance.get_signature.return_value = "void foo()"
|
||||
|
||||
result = dispatch("ts_cpp_get_signature", {"path": str(cpp_file), "name": "foo"})
|
||||
|
||||
mock_parser_cls.assert_called_once_with("cpp")
|
||||
mock_instance.get_signature.assert_called_once()
|
||||
assert "void foo()" in result
|
||||
|
||||
def test_ts_c_update_definition_dispatch(tmp_path, mock_resolve):
|
||||
# Verify ts_c_update_definition via dispatch
|
||||
c_file = tmp_path / "test.c"
|
||||
c_file.write_text("void foo() {}")
|
||||
|
||||
with patch("src.file_cache.ASTParser") as mock_parser_cls:
|
||||
mock_instance = mock_parser_cls.return_value
|
||||
mock_instance.update_definition.return_value = "void foo() { return; }"
|
||||
|
||||
result = dispatch("ts_c_update_definition", {"path": str(c_file), "name": "foo", "new_content": "void foo() { return; }"})
|
||||
|
||||
mock_parser_cls.assert_called_once_with("c")
|
||||
mock_instance.update_definition.assert_called_once()
|
||||
assert "Successfully updated" in result
|
||||
|
||||
def test_ts_cpp_update_definition_dispatch(tmp_path, mock_resolve):
|
||||
# Verify ts_cpp_update_definition via dispatch
|
||||
cpp_file = tmp_path / "test.cpp"
|
||||
cpp_file.write_text("void foo() {}")
|
||||
|
||||
with patch("src.file_cache.ASTParser") as mock_parser_cls:
|
||||
mock_instance = mock_parser_cls.return_value
|
||||
mock_instance.update_definition.return_value = "void foo() { return; }"
|
||||
|
||||
result = dispatch("ts_cpp_update_definition", {"path": str(cpp_file), "name": "foo", "new_content": "void foo() { return; }"})
|
||||
|
||||
mock_parser_cls.assert_called_once_with("cpp")
|
||||
mock_instance.update_definition.assert_called_once()
|
||||
assert "Successfully updated" in result
|
||||
|
||||
Reference in New Issue
Block a user