Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions cecli/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
MarkdownHelpFormatter,
YamlHelpFormatter,
)
from cecli.deprecated_args import add_deprecated_model_args
from cecli.deprecated_args import add_deprecated_mcp_args, add_deprecated_model_args

from .dump import dump # noqa: F401

Expand Down Expand Up @@ -364,10 +364,11 @@ def get_parser(default_config_files, git_root):
default=None,
)
group.add_argument(
"--mcp-servers-file",
metavar="MCP_CONFIG_FILE",
help="Specify a file path with MCP server configurations",
default=None,
"--mcp-servers-files",
metavar="MCP_CONFIG_FILES",
help="Specify a file path with MCP server configurations (can be specified multiple times)",
action="append",
default=[],
)
group.add_argument(
"--mcp-transport",
Expand Down Expand Up @@ -1089,6 +1090,9 @@ def get_parser(default_config_files, git_root):
# Add deprecated model shortcut arguments
add_deprecated_model_args(parser, group)

group = parser.add_argument_group("Deprecated agent settings")
add_deprecated_mcp_args(parser, group)

return parser


Expand Down
11 changes: 11 additions & 0 deletions cecli/deprecated_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,17 @@ def add_deprecated_model_args(parser, group):
)


def add_deprecated_mcp_args(parser, group):
"""Add deprecated mcp arguments to the argparse parser."""
group.add_argument(
"--mcp-servers-file",
help=argparse.SUPPRESS,
action="append",
dest="mcp_servers_file_deprecated",
env_var="CECLI_MCP_SERVERS_FILE",
)


def handle_deprecated_model_args(args, io):
"""Handle deprecated model shortcut arguments and provide appropriate warnings."""
# Define model mapping
Expand Down
11 changes: 10 additions & 1 deletion cecli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1065,8 +1065,17 @@ def apply_model_overrides(model_name):
# Default since some models do not have max_input_tokens specified somehow
args.context_compaction_max_tokens = 65536
try:
if getattr(args, "mcp_servers_file_deprecated", None):
io.tool_warning(
"The --mcp-servers-file argument is deprecated and will be removed in a future"
" version. Please use --mcp-servers-files instead."
)
if not args.mcp_servers_files:
args.mcp_servers_files = []
args.mcp_servers_files.extend(args.mcp_servers_file_deprecated)

mcp_servers = load_mcp_servers(
args.mcp_servers, args.mcp_servers_file, io, args.verbose, args.mcp_transport
args.mcp_servers, args.mcp_servers_files, io, args.verbose, args.mcp_transport
)
mcp_manager = await McpServerManager.from_servers(mcp_servers, io, args.verbose)
# Load hooks if specified
Expand Down
13 changes: 9 additions & 4 deletions cecli/mcp/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def _parse_mcp_servers_from_file(file_path, io, verbose=False, mcp_transport="st


def load_mcp_servers(
mcp_servers, mcp_servers_file, io, verbose=False, mcp_transport="stdio"
mcp_servers, mcp_servers_files, io, verbose=False, mcp_transport="stdio"
) -> list["McpServer"]:
"""Load MCP servers from a JSON string or file."""
servers = []
Expand All @@ -160,9 +160,14 @@ def load_mcp_servers(
if servers:
return servers

# If JSON string failed or wasn't provided, try the file
if mcp_servers_file:
servers = _parse_mcp_servers_from_file(mcp_servers_file, io, verbose, mcp_transport)
# If JSON string failed or wasn't provided, try the files
if mcp_servers_files:
servers = []
for mcp_servers_file in mcp_servers_files:
file_servers = _parse_mcp_servers_from_file(
mcp_servers_file, io, verbose, mcp_transport
)
servers.extend(file_servers)

if not servers:
# A default MCP server is actually now necessary for the overall agentic loop
Expand Down
42 changes: 41 additions & 1 deletion tests/basic/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1373,11 +1373,51 @@ def test_mcp_servers_parsing(dummy_io, git_temp_dir, mocker):
mcp_file = Path("mcp_servers.json")
mcp_content = {"mcpServers": {"git": {"command": "uvx", "args": ["mcp-server-git"]}}}
mcp_file.write_text(json.dumps(mcp_content))
main(["--mcp-servers-file", str(mcp_file), "--exit", "--yes-always"], **dummy_io)
main(["--mcp-servers-files", str(mcp_file), "--exit", "--yes-always"], **dummy_io)
mock_coder_create.assert_called_once()
_, kwargs = mock_coder_create.call_args

assert "mcp_manager" in kwargs
assert kwargs["mcp_manager"] is not None
assert len(kwargs["mcp_manager"].servers) > 0
assert hasattr(kwargs["mcp_manager"].servers[0], "name")


def test_mcp_servers_file_multiple(dummy_io, git_temp_dir, mocker):
mocker.patch("cecli.mcp.server.McpServer.connect", new_callable=AsyncMock)
mock_coder_create = mocker.patch("cecli.coders.Coder.create")
mock_coder_instance = MagicMock()
mock_coder_instance.mcp_manager = False
mock_coder_instance._autosave_future = mock_autosave_future()
mock_coder_create.return_value = mock_coder_instance

mcp_file1 = Path("mcp_servers1.json")
mcp_content1 = {"mcpServers": {"server1": {"command": "cmd1"}}}
mcp_file1.write_text(json.dumps(mcp_content1))

mcp_file2 = Path("mcp_servers2.json")
mcp_content2 = {"mcpServers": {"server2": {"command": "cmd2"}}}
mcp_file2.write_text(json.dumps(mcp_content2))

main(
[
"--mcp-servers-files",
str(mcp_file1),
"--mcp-servers-files",
str(mcp_file2),
"--exit",
"--yes-always",
],
**dummy_io,
)

mock_coder_create.assert_called_once()
_, kwargs = mock_coder_create.call_args

assert "mcp_manager" in kwargs
mcp_manager = kwargs["mcp_manager"]
assert mcp_manager is not None
assert len(mcp_manager.servers) == 2
server_names = {server.name for server in mcp_manager.servers}
assert "server1" in server_names
assert "server2" in server_names
Loading