Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f862e9a
cli-41: allow agents load remove mcp
May 31, 2026
05f9bfb
Merge branch 'main' of github.com-personal:cecli-dev/cecli into cli-4…
Jun 1, 2026
f53dab4
fixed
Jun 1, 2026
90ecacd
feat: Update test implementation and cases for MCP tools
Jun 16, 2026
f800aa5
fix: Update load_mcp_tool and its tests
Jun 16, 2026
a2edd66
fix: Correctly mock async function and test non-existent server
Jun 16, 2026
b8d1b42
fix: Correct test_load_mcp_tool_wildcard_and_duplicate_fix assertions
Jun 16, 2026
9ca46ef
#581: total_cost does not need to be propagated on coder class creati…
Jun 19, 2026
bb40e00
Change hashpos system to use base 256 numeric system instead of base…
Jun 19, 2026
31bbf22
Strip multiple layers of hash fragments from start of strings in edit…
Jun 19, 2026
5b0726b
Update hashpos messaging in agent modes
Jun 19, 2026
7cab27a
Update tools:
Jun 20, 2026
2ac74c8
cli-41: add tests
Jun 20, 2026
7826ce5
fix: Add missing coroutines attribute and fix status_bar access
Jun 20, 2026
fcebc49
fix: Resolve AttributeError in AgentCoder when switching to /agent mode
Jun 20, 2026
0028706
fix: Use self.coroutines.interruptible in agent_coder.py
Jun 20, 2026
5534d2c
Add real examples from compressed hashpos in system prompts
Jun 21, 2026
9e76b89
refactor: Rename load_mcp_tool and remove_mcp_tool files and classes
Jun 21, 2026
c35ca01
refactor: Rename load_mcp and remove_mcp tool files and classes
Jun 21, 2026
2504fc3
refactor: Add mcp_servers context to AgentCoder
Jun 21, 2026
a06b4f9
feat: Add ListMcp tool and command
Jun 21, 2026
5390587
chore: Remove unused blank line in agent_coder.py
Jun 21, 2026
c56dc0b
fix
Jun 21, 2026
23a1955
Don't load coder class definitions into memory unless actually insant…
Jun 21, 2026
b566125
Parse models out of json instead of deserializing entire large metada…
Jun 21, 2026
e992feb
Defer large imports of tree-sitter to improve start up time
Jun 21, 2026
064d3fc
#583: Add secondary command confirmation for allowing command for the…
Jun 21, 2026
70eba9f
Address frequent pandoc failures in CI/CD runs
Jun 21, 2026
c9dbb96
cli-41: relabled available mcps to configured, to not confuse configu…
Jun 21, 2026
1c1b634
cli-41
Jun 21, 2026
5877833
#537: Add lazy imports to main.py to help improve startup speed
Jun 21, 2026
a6b5795
cli-41: merged
Jun 21, 2026
dfc7bab
cli-41: list
Jun 21, 2026
9f7a441
Add annotation helper for ci/cd
Jun 21, 2026
159fa6b
Update metadata
Jun 21, 2026
aa58482
refactor: Add MCP server management guidance to agent coder
Jun 21, 2026
04ffd2f
Lazy load litellm in mcp manager
Jun 21, 2026
32b2ef0
Update documentation
Jun 21, 2026
91c9f4d
Compact file and diff messages on compaction pressure
Jun 21, 2026
88eb1b7
Merge remote-tracking branch 'szmania/cli-41-allow-agents-mcp-managem…
Jun 21, 2026
9d4d0c6
Remove extraneous coder class changes
Jun 21, 2026
f40f182
Make `/list-mcp` sub agent sensitive
Jun 21, 2026
46fd622
Make load and remove MCP tools match their command counterparts
Jun 21, 2026
4e60699
Move MCP and skill management to common `ResourceManager` tool
Jun 22, 2026
7cf2971
#579: Editable on non-existent file creates it
Jun 22, 2026
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
93 changes: 54 additions & 39 deletions cecli/coders/__init__.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,57 @@
from .agent_coder import AgentCoder
from .architect_coder import ArchitectCoder
from .ask_coder import AskCoder
from .base_coder import Coder
from .context_coder import ContextCoder
from .copypaste_coder import CopyPasteCoder
from .editblock_coder import EditBlockCoder
from .editblock_fenced_coder import EditBlockFencedCoder
from .editor_diff_fenced_coder import EditorDiffFencedCoder
from .editor_editblock_coder import EditorEditBlockCoder
from .editor_whole_coder import EditorWholeFileCoder
from .hashline_coder import HashLineCoder
from .help_coder import HelpCoder
from .patch_coder import PatchCoder
from .sub_agent_coder import SubAgentCoder
from .udiff_coder import UnifiedDiffCoder
from .udiff_simple import UnifiedDiffSimpleCoder
from .wholefile_coder import WholeFileCoder

# from .single_wholefile_func_coder import SingleWholeFileFunctionCoder
"""Coder module with lazy imports to reduce startup memory."""

__all__ = [
HelpCoder,
AskCoder,
Coder,
EditBlockCoder,
EditBlockFencedCoder,
WholeFileCoder,
PatchCoder,
UnifiedDiffCoder,
UnifiedDiffSimpleCoder,
# SingleWholeFileFunctionCoder,
ArchitectCoder,
EditorEditBlockCoder,
EditorWholeFileCoder,
EditorDiffFencedCoder,
ContextCoder,
AgentCoder,
CopyPasteCoder,
HashLineCoder,
SubAgentCoder,
"HelpCoder",
"AskCoder",
"Coder",
"EditBlockCoder",
"EditBlockFencedCoder",
"WholeFileCoder",
"PatchCoder",
"UnifiedDiffCoder",
"UnifiedDiffSimpleCoder",
"ArchitectCoder",
"EditorEditBlockCoder",
"EditorWholeFileCoder",
"EditorDiffFencedCoder",
"ContextCoder",
"AgentCoder",
"CopyPasteCoder",
"HashLineCoder",
"SubAgentCoder",
]

# Module name mapping (snake_case to class name)
_MODULE_MAP = {
"HelpCoder": ".help_coder",
"AskCoder": ".ask_coder",
"Coder": ".base_coder",
"EditBlockCoder": ".editblock_coder",
"EditBlockFencedCoder": ".editblock_fenced_coder",
"WholeFileCoder": ".wholefile_coder",
"PatchCoder": ".patch_coder",
"UnifiedDiffCoder": ".udiff_coder",
"UnifiedDiffSimpleCoder": ".udiff_simple",
"ArchitectCoder": ".architect_coder",
"EditorEditBlockCoder": ".editor_editblock_coder",
"EditorWholeFileCoder": ".editor_whole_coder",
"EditorDiffFencedCoder": ".editor_diff_fenced_coder",
"ContextCoder": ".context_coder",
"AgentCoder": ".agent_coder",
"CopyPasteCoder": ".copypaste_coder",
"HashLineCoder": ".hashline_coder",
"SubAgentCoder": ".sub_agent_coder",
}


def __getattr__(name):
if name in _MODULE_MAP:
import importlib

mod = importlib.import_module(_MODULE_MAP[name], __package__)
return getattr(mod, name)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


def __dir__():
return __all__
97 changes: 87 additions & 10 deletions cecli/coders/agent_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ def _get_agent_config(self):
"todo_list",
"sub_agents",
"skills",
"servers",
},
)
)
Expand Down Expand Up @@ -357,6 +358,7 @@ def _calculate_context_block_tokens(self, force=False):
"git_status",
"symbol_outline",
"skills",
"servers",
"sub_agents",
"loaded_skills",
]
Expand Down Expand Up @@ -391,6 +393,8 @@ def _generate_context_block(self, block_name):
content = self.get_todo_list()
elif block_name == "skills":
content = self.get_skills_context()
elif block_name == "servers":
content = self.get_servers_context()
elif block_name == "loaded_skills":
content = self.get_skills_content()
elif block_name == "sub_agents" and (
Expand Down Expand Up @@ -624,8 +628,8 @@ def get_context_summary(self):
result += f" ({percentage:.1f}% of limit)"
if percentage > 80:
result += "\n\n⚠ **Context is getting full!**\n"
result += "- Remove non-essential files via the `ContextManager` tool.\n"
result += "- Keep only essential files in context for best performance"
result += "- Remove non-essential files, skills and tool servers via the `ResourceManager` tool.\n"
result += "- Keep only essential files, skills, and MCP servers in context for best performance"
result += "\n</context>"
if not hasattr(self, "context_blocks_cache"):
self.context_blocks_cache = {}
Expand Down Expand Up @@ -659,7 +663,9 @@ def get_environment_info(self):
result += f"- Git repository: {rel_repo_dir} with {num_files:,} files\n"
except Exception:
result += "- Git repository: active but details unavailable\n"
else:
if self.mcp_manager and self.mcp_manager.connected_servers:
num_mcp_servers = len(self.mcp_manager.connected_servers)
result += f"- Connected MCP servers: {num_mcp_servers}\n"
result += "- Git repository: none\n"
result += "</context>"
return result
Expand Down Expand Up @@ -806,9 +812,7 @@ async def gather_and_await():
if self.auto_lint and used_write_tool:
edited = list(self.files_edited_by_tools)
lint_coro = self.lint_edited(edited, show_output=False)
lint_errors, interrupted = await self.coroutines.interruptible(
lint_coro, self.interrupt_event
)
lint_errors, interrupted = await interruptible(lint_coro, self.interrupt_event)
if interrupted:
raise KeyboardInterrupt("Interrupted during linting")

Expand Down Expand Up @@ -928,9 +932,7 @@ async def reply_completed(self):
)
self.io.tool_output(waiting_msg)
sleep_coro = asyncio.sleep(command_timeout / 2)
_res, interrupted = await self.coroutines.interruptible(
sleep_coro, self.interrupt_event
)
_res, interrupted = await interruptible(sleep_coro, self.interrupt_event)
if interrupted:
raise KeyboardInterrupt("Interrupted while waiting for background commands")
return True
Expand Down Expand Up @@ -1329,7 +1331,7 @@ async def check_for_file_mentions(self, content):

Override parent's method to disable implicit file mention handling in agent mode.
Files should only be added via explicit tool commands
(`ContextManager`).
(`ResourceManager`).
"""
pass

Expand Down Expand Up @@ -1483,6 +1485,81 @@ def get_skills_content(self):
self.io.tool_error(f"Error generating skills content context: {str(e)}")
return None

def get_servers_context(self):
"""
Generate a context block for available MCP servers.

Categorizes servers as:
- Active: Connected and passing includelist/excludelist filters
- Inactive: Connected but filtered out by includelist/excludelist
- Available (Disconnected): Managed but not currently connected

Returns:
Formatted context block string or None if no servers available
"""
if not self.use_enhanced_context:
return None
try:
if not self.mcp_manager:
return None

all_servers = self.mcp_manager.servers
connected_servers = self.mcp_manager.connected_servers
connected_server_names = {s.name for s in connected_servers}

if not all_servers:
return None

# Apply registered_servers filtering to determine active vs inactive
incl = self.registered_servers.get("included", set())
excl = self.registered_servers.get("excluded", set())

active_servers = []
inactive_servers = []
for server in connected_servers:
name = server.name
if incl and name not in incl:
inactive_servers.append(name)
elif name in excl:
inactive_servers.append(name)
else:
active_servers.append(name)

# Servers managed but not currently connected
disconnected_servers = [
server.name for server in all_servers if server.name not in connected_server_names
]

result = '<context name="servers" from="agent">\n'
result += "## Connected MCP Servers\n\n"

if active_servers:
result += f"Active ({len(active_servers)}):\n"
for name in sorted(active_servers):
result += f"- {name}\n"
result += "\n"

if inactive_servers:
result += f"Inactive (Filtered) ({len(inactive_servers)}):\n"
for name in sorted(inactive_servers):
result += f"- {name}\n"
result += "\n"

if disconnected_servers:
result += f"Available (Disconnected) ({len(disconnected_servers)}):\n"
for name in sorted(disconnected_servers):
result += f"- {name}\n"
result += "\n"

if not active_servers and not inactive_servers and not disconnected_servers:
result += "No MCP servers currently available.\n\n"

result += "</context>"
return result
except Exception as e:
self.io.tool_error(f"Error generating servers context: {str(e)}")
return None

def get_sub_agents_context(self):
"""
Generate a context block for registered sub-agents.
Expand Down
1 change: 0 additions & 1 deletion cecli/coders/architect_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ async def reply_completed(self):
kwargs["args"] = self.args
kwargs["suggest_shell_commands"] = False
kwargs["map_tokens"] = 0
kwargs["total_cost"] = self.total_cost
kwargs["cache_prompts"] = False
kwargs["num_cache_warming_pings"] = 0
kwargs["summarize_from_coder"] = False
Expand Down
Loading
Loading