Skip to content

Commit 245ec74

Browse files
committed
Revamp subtool/command distinction
Signed-off-by: Edward Z. Yang <ezyang@mit.edu>
1 parent 4cb919e commit 245ec74

File tree

7 files changed

+73
-76
lines changed

7 files changed

+73
-76
lines changed

ARCHITECTURE.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ This document provides an overview of the architecture and design decisions of c
66

77
The codemcp tool uses a TOML file (`codemcp.toml`) in the project root for configuration. This file has several sections:
88

9-
### Global Prompt
9+
### Project Prompt
1010

11-
The `global_prompt` string is included in system prompts to provide project-specific instructions to Claude.
11+
The `project_prompt` string is included in system prompts to provide project-specific instructions to Claude.
1212

1313
```toml
14-
global_prompt = """
14+
project_prompt = """
1515
Project-specific instructions for Claude go here.
1616
"""
1717
```
@@ -45,7 +45,7 @@ codemcp provides several tools that Claude can use during interaction:
4545
When a project is initialized using `InitProject`, codemcp reads the `codemcp.toml` file and constructs a system prompt that includes:
4646

4747
1. Default system instructions
48-
2. The project's `global_prompt`
48+
2. The project's `project_prompt`
4949
3. Instructions to use specific tools at appropriate times
5050

5151
For example, if a format command is configured, the system prompt will include an instruction for Claude to use the Format tool when the task is complete.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ branch from the default branch. Suppose that codemcp is operating on
109109
Here are all the config options supported by `codemcp.toml`:
110110

111111
```toml
112-
global_prompt = """
112+
project_prompt = """
113113
Before beginning work on this feature, write a short haiku. Do this only once.
114114
"""
115115

@@ -118,7 +118,7 @@ format = ["./run_format.sh"]
118118
test = ["./run_test.sh"]
119119
```
120120

121-
The `global_prompt` will be loaded when you initialize the project in chats.
121+
The `project_prompt` will be loaded when you initialize the project in chats.
122122

123123
The `commands` section allows you to configure commands for specific tools:
124124
- `format`: used to format code according to project standards. Formatting is

codemcp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
global_prompt = """
1+
project_prompt = """
22
Before beginning work on this feature, write a short haiku. Do this only once.
33
When you are done with your task, run format and lint commands.
44
"""

codemcp/main.py

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
@mcp.tool()
2222
async def codemcp(
2323
ctx: Context,
24-
command: str,
24+
subtool: str,
2525
*,
2626
path: str | None = None,
2727
content: str | None = None,
@@ -33,7 +33,7 @@ async def codemcp(
3333
pattern: str | None = None,
3434
include: str | None = None,
3535
test_selector: str | None = None,
36-
command_type: str | None = None,
36+
command: str | None = None,
3737
arguments: list[str] | None = None,
3838
old_str: str | None = None, # Added for backward compatibility
3939
new_str: str | None = None, # Added for backward compatibility
@@ -44,11 +44,11 @@ async def codemcp(
4444
IMMEDIATELY follow before continuing.
4545
4646
Arguments:
47-
command: The command to run (InitProject, ...)
47+
subtool: The subtool to run (InitProject, ...)
4848
path: The path to the file or directory to operate on
4949
... (there are other arguments which are documented later)
5050
"""
51-
# Define expected parameters for each command
51+
# Define expected parameters for each subtool
5252
expected_params = {
5353
"ReadFile": {"path", "offset", "limit"},
5454
"WriteFile": {"path", "content", "description"},
@@ -66,9 +66,9 @@ async def codemcp(
6666
"Grep": {"pattern", "path", "include"},
6767
}
6868

69-
# Check if command exists
70-
if command not in expected_params:
71-
return f"Unknown command: {command}. Available commands: {', '.join(expected_params.keys())}"
69+
# Check if subtool exists
70+
if subtool not in expected_params:
71+
return f"Unknown subtool: {subtool}. Available subtools: {', '.join(expected_params.keys())}"
7272

7373
# Get all provided non-None parameters
7474
provided_params = {
@@ -84,7 +84,7 @@ async def codemcp(
8484
"pattern": pattern,
8585
"include": include,
8686
"test_selector": test_selector,
87-
"command_type": command_type,
87+
"command": command,
8888
"arguments": arguments,
8989
# Include backward compatibility parameters
9090
"old_str": old_str,
@@ -94,67 +94,72 @@ async def codemcp(
9494
}
9595

9696
# Check for unexpected parameters
97-
unexpected_params = set(provided_params.keys()) - expected_params[command]
97+
unexpected_params = set(provided_params.keys()) - expected_params[subtool]
9898
if unexpected_params:
99-
return f"Error: Unexpected parameters for {command} command: {', '.join(unexpected_params)}"
99+
return f"Error: Unexpected parameters for {subtool} subtool: {', '.join(unexpected_params)}"
100100

101-
# Now handle each command with its expected parameters
102-
if command == "ReadFile":
101+
# Now handle each subtool with its expected parameters
102+
if subtool == "ReadFile":
103103
if path is None:
104-
return "Error: path is required for ReadFile command"
104+
return "Error: path is required for ReadFile subtool"
105105

106106
return read_file_content(path, offset, limit)
107107

108-
if command == "WriteFile":
108+
if subtool == "WriteFile":
109109
if path is None:
110-
return "Error: path is required for WriteFile command"
110+
return "Error: path is required for WriteFile subtool"
111111
if description is None:
112-
return "Error: description is required for WriteFile command"
112+
return "Error: description is required for WriteFile subtool"
113113

114114
content_str = content or ""
115115
return write_file_content(path, content_str, description)
116116

117-
if command == "EditFile":
117+
if subtool == "EditFile":
118118
if path is None:
119-
return "Error: path is required for EditFile command"
119+
return "Error: path is required for EditFile subtool"
120120
if description is None:
121-
return "Error: description is required for EditFile command"
121+
return "Error: description is required for EditFile subtool"
122122
if old_string is None and old_str is None:
123123
# TODO: I want telemetry to tell me when this occurs.
124-
return "Error: Either old_string or old_str is required for EditFile command (use empty string for new file creation)"
124+
return "Error: Either old_string or old_str is required for EditFile subtool (use empty string for new file creation)"
125125

126126
# Accept either old_string or old_str (prefer old_string if both are provided)
127127
old_content = old_string or old_str or ""
128128
# Accept either new_string or new_str (prefer new_string if both are provided)
129129
new_content = new_string or new_str or ""
130130
return edit_file_content(path, old_content, new_content, None, description)
131131

132-
if command == "LS":
132+
if subtool == "LS":
133133
if path is None:
134-
return "Error: path is required for LS command"
134+
return "Error: path is required for LS subtool"
135135

136136
return ls_directory(path)
137137

138-
if command == "InitProject":
138+
if subtool == "InitProject":
139139
if path is None:
140-
return "Error: path is required for InitProject command"
140+
return "Error: path is required for InitProject subtool"
141141

142142
return init_project(path)
143143

144-
if command == "RunCommand":
144+
if subtool == "RunCommand":
145+
# When is something a command as opposed to a subtool? They are
146+
# basically the same thing, but commands are always USER defined.
147+
# This means we shove them all in RunCommand so they are guaranteed
148+
# not to conflict with codemcp's subtools.
149+
145150
if path is None:
146-
return "Error: path is required for RunCommand command"
147-
if command_type is None:
148-
return "Error: command_type is required for RunCommand command"
151+
return "Error: path is required for RunCommand subtool"
152+
if command is None:
153+
return "Error: command is required for RunCommand subtool"
149154

150-
return run_command(path, command_type, arguments)
155+
return run_command(path, command, arguments)
151156

152-
if command == "Grep":
157+
if subtool == "Grep":
153158
if pattern is None:
154-
return "Error: pattern is required for Grep command"
159+
return "Error: pattern is required for Grep subtool"
155160

156161
if path is None:
157-
return "Error: path is required for Grep command"
162+
return "Error: path is required for Grep subtool"
158163

159164
try:
160165
result = grep_files(pattern, path, include)

codemcp/tools/init_project.py

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def init_project(directory: str) -> str:
3838
directory: The directory path containing the codemcp.toml file
3939
4040
Returns:
41-
A string containing the system prompt plus any global_prompt from the config
41+
A string containing the system prompt plus any project_prompt from the config
4242
4343
"""
4444
try:
@@ -55,7 +55,7 @@ def init_project(directory: str) -> str:
5555
# Build path to codemcp.toml file
5656
rules_file_path = os.path.join(full_dir_path, "codemcp.toml")
5757

58-
global_prompt = ""
58+
project_prompt = ""
5959
command_help = ""
6060
command_docs = {}
6161
rules_config = {}
@@ -66,9 +66,9 @@ def init_project(directory: str) -> str:
6666
with open(rules_file_path, "rb") as f:
6767
rules_config = tomli.load(f)
6868

69-
# Extract global_prompt if it exists
70-
if "global_prompt" in rules_config:
71-
global_prompt = rules_config["global_prompt"]
69+
# Extract project_prompt if it exists
70+
if "project_prompt" in rules_config:
71+
project_prompt = rules_config["project_prompt"]
7272

7373
# Extract commands and their documentation
7474
command_list = rules_config.get("commands", {})
@@ -117,7 +117,7 @@ def init_project(directory: str) -> str:
117117
- If you intend to call multiple tools and there are no dependencies between the calls, make all of the independent calls in the same function_calls block.
118118
119119
# codemcp tool
120-
The codemcp tool supports a number of subcommands which you should use to perform coding tasks.
120+
The codemcp tool supports a number of subtools which you should use to perform coding tasks.
121121
122122
## ReadFile path offset? limit?
123123
@@ -201,7 +201,7 @@ def init_project(directory: str) -> str:
201201
Grep "function.*hello" /path/to/repo # Find files containing functions with "hello" in their name
202202
Grep "console\\.log" /path/to/repo --include="*.js" # Find JS files with console.log statements
203203
204-
## RunCommand path command_type arguments?
204+
## RunCommand path command arguments?
205205
206206
Runs a command. This does NOT support arbitrary code execution, ONLY call
207207
with this set of valid commands: {command_help}
@@ -210,29 +210,21 @@ def init_project(directory: str) -> str:
210210
## Summary
211211
212212
Args:
213-
command: The subcommand to execute (ReadFile, WriteFile, EditFile, LS, InitProject, RunCommand)
213+
subtool: The subtool to execute (ReadFile, WriteFile, EditFile, LS, InitProject, RunCommand)
214214
path: The path to the file or directory to operate on
215-
content: Content for WriteFile command
216-
old_string: String to replace for EditFile command
217-
new_string: Replacement string for EditFile command
218-
offset: Line offset for ReadFile command
219-
limit: Line limit for ReadFile command
215+
content: Content for WriteFile subtool
216+
old_string: String to replace for EditFile subtool
217+
new_string: Replacement string for EditFile subtool
218+
offset: Line offset for ReadFile subtool
219+
limit: Line limit for ReadFile subtool
220220
description: Short description of the change (for WriteFile/EditFile)
221-
arguments: A list of string arguments for RunCommand command
221+
arguments: A list of string arguments for RunCommand subtool
222222
"""
223-
format_command_str = ""
224-
# Check if format command is configured
225-
if "commands" in rules_config and "format" in rules_config["commands"]:
226-
format_command_str = (
227-
"\n\nYou can also run code formatting using the Format tool."
228-
)
229-
230-
# Combine system prompt, global prompt, and format command
223+
224+
# Combine system prompt, global prompt
231225
combined_prompt = system_prompt
232-
if global_prompt:
233-
combined_prompt += "\n\n" + global_prompt
234-
if format_command_str:
235-
combined_prompt += format_command_str
226+
if project_prompt:
227+
combined_prompt += "\n\n" + project_prompt
236228

237229
return combined_prompt
238230
except Exception as e:

codemcp/tools/run_command.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,25 @@
1010

1111

1212
def run_command(
13-
project_dir: str, command_type: str, arguments: Optional[List[str]] = None
13+
project_dir: str, command: str, arguments: Optional[List[str]] = None
1414
) -> str:
1515
"""Run a command that is configured in codemcp.toml.
1616
1717
Args:
1818
project_dir: The directory path containing the codemcp.toml file
19-
command_type: The type of command to run (e.g., "format", "lint", "test")
19+
command: The type of command to run (e.g., "format", "lint", "test")
2020
arguments: Optional list of arguments to pass to the command
2121
2222
Returns:
2323
A string containing the result of the command operation
2424
"""
25-
command_list = get_command_from_config(project_dir, command_type)
25+
command_list = get_command_from_config(project_dir, command)
2626

2727
# If arguments are provided, extend the command with them
2828
if arguments and command_list:
2929
command_list = command_list.copy()
3030
command_list.extend(arguments)
3131

3232
return run_code_command(
33-
project_dir, command_type, command_list, f"Auto-commit {command_type} changes"
33+
project_dir, command, command_list, f"Auto-commit {command} changes"
3434
)

test/unit/test_init_project.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ def test_init_project_no_rules_file(self):
4444

4545
def test_init_project_with_rules_file(self):
4646
"""Test initializing a project with a codemcp.toml file."""
47-
# Create a codemcp.toml file with a global_prompt
47+
# Create a codemcp.toml file with a project_prompt
4848
rules_file_path = os.path.join(self.dir_path, "codemcp.toml")
4949
with open(rules_file_path, "w") as f:
50-
f.write('global_prompt = "This is a custom global prompt."\n')
50+
f.write('project_prompt = "This is a custom global prompt."\n')
5151

5252
result = init_project(self.dir_path)
5353
# Check for a stable part of the system prompt and the custom global prompt
@@ -76,7 +76,7 @@ def test_init_project_invalid_toml(self):
7676
rules_file_path = os.path.join(self.dir_path, "codemcp.toml")
7777
with open(rules_file_path, "w") as f:
7878
f.write(
79-
'global_prompt = "This is an invalid TOML file\n',
79+
'project_prompt = "This is an invalid TOML file\n',
8080
) # Missing closing quote
8181

8282
result = init_project(self.dir_path)
@@ -88,7 +88,7 @@ def test_format_not_exposed_when_not_configured(self):
8888
rules_file_path = os.path.join(self.dir_path, "codemcp.toml")
8989
with open(rules_file_path, "w") as f:
9090
f.write(
91-
'global_prompt = "This is a global prompt without formatter config."\n'
91+
'project_prompt = "This is a global prompt without formatter config."\n'
9292
)
9393

9494
result = init_project(self.dir_path)
@@ -101,7 +101,7 @@ def test_command_docs_exposed_when_configured(self):
101101
rules_file_path = os.path.join(self.dir_path, "codemcp.toml")
102102
with open(rules_file_path, "w") as f:
103103
f.write("""
104-
global_prompt = "This is a global prompt with command docs."
104+
project_prompt = "This is a global prompt with command docs."
105105
[commands]
106106
format = ["./run_format.sh"]
107107
[commands.test]
@@ -123,7 +123,7 @@ def test_multiple_command_docs(self):
123123
rules_file_path = os.path.join(self.dir_path, "codemcp.toml")
124124
with open(rules_file_path, "w") as f:
125125
f.write("""
126-
global_prompt = "This is a global prompt with multiple command docs."
126+
project_prompt = "This is a global prompt with multiple command docs."
127127
[commands]
128128
format = ["./run_format.sh"]
129129
[commands.test]
@@ -146,7 +146,7 @@ def test_no_command_docs(self):
146146
rules_file_path = os.path.join(self.dir_path, "codemcp.toml")
147147
with open(rules_file_path, "w") as f:
148148
f.write("""
149-
global_prompt = "This is a global prompt without command docs."
149+
project_prompt = "This is a global prompt without command docs."
150150
[commands]
151151
format = ["./run_format.sh"]
152152
test = ["./run_test.sh"]

0 commit comments

Comments
 (0)