About Crow CLI
Crow CLI is an MCP server published by crow-cli in the Developer Tools category: minimal MCP based ACP agent.
The repository has 62 stars and 8 forks, with the last commit 6 days ago. Recent commit activity is the strongest signal that reported issues will get attention.
Install
git clone https://github.com/crow-cli/crow-cliThis server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.
Tools (1)
terminalRun shell commands in the workspace
Parameters not published.
Ask AI
Ask AI about Crow CLI
Powered by Claude · Grounded in docs
Security checks
- ✓README presentThe repository ships documentation.
5 checks have not been run yet.
README
🐦⬛ Crow
Monorepo for crow-cli and crow-mcp.
Crow is a two-layer system: an ACP agent (crow-cli) that does the thinking, and an MCP toolserver (crow-mcp) that does the doing.

Architecture
User → ACP Client → crow-cli (AcpAgent)
↓
ReAct Loop ←→ LLM (OpenAI-compatible)
↓
Tool Dispatch
├── ACP Client Terminal (if supported)
└── crow-mcp (MCP Server)
├── terminal (PTY)
├── read/write/edit
├── web_search (SearXNG)
└── web_fetch (readabilipy)
crow-mcp — The Toolserver
A FastMCP server that exposes 7 tools:
| Tool | What it does |
|---|---|
terminal |
PTY-backed bash session. Spawns a real pseudoterminal (pty.openpty()), sets a custom PS1 prompt with metadata (exit code, cwd), runs commands, and polls for completion via PS1 detection. Supports C-c/C-z/C-d special keys, stdin input, soft timeout (30s no output), and hard timeout. |
read |
Reads files with line numbers, binary detection, 10MB limit, pagination via offset/limit. |
write |
File writer with auto mkdir -p. |
edit |
9 cascading fuzzy matchers for string replacement: exact → line-trimmed → block-anchor (Levenshtein) → whitespace-normalized → indentation-flexible → escape-normalized → trimmed-boundary → context-aware (50% middle match) → multi-occurrence. Falls through until one matches. |
web_search |
Queries a local SearXNG instance, returns structured results. |
web_fetch |
Fetches URLs, uses readabilipy + markdownify to extract clean markdown from HTML. Supports pagination. |
The terminal backend uses a background threading.Thread that continuously reads from the PTY master fd via select(), with a deque buffer and proper signal handling (SIGINT to process group, SIGTERM→SIGKILL cleanup).
crow-cli — The Agent Brain
An ACP-native agent (implements the Agent Communication Protocol) with these key components:
AcpAgent — ACP Protocol Implementation
- Implements
Agentinterface:initialize,new_session,load_session,prompt,cancel,cleanup - Manages
AsyncExitStackfor resource lifecycle - On
new_session: loads system prompt from Jinja2 template, readsAGENTS.mdfrom workspace, builds directory tree, creates MCP client, stores session - On
prompt: spawns the ReAct loop as anasyncio.Taskfor cancellation support, streams chunks back to client via ACP updates - Supports model switching at runtime via
set_config_option - Detects client capabilities — if the ACP client supports terminals, uses client-side terminals; otherwise falls back to MCP terminals
react_loop — The ReAct Loop
- Classic Reason+Act loop, up to 50,000 turns
- Streams LLM responses via OpenAI-compatible API with exponential backoff retries
process_chunkhandles streaming deltas: content tokens, thinking/reasoning tokens, and tool call accumulation- After each LLM response, checks token usage against
MAX_COMPACT_TOKENS(120k) and triggers compaction if exceeded - Tool execution is dual-mode: ACP client terminals for supported clients, MCP tools for everything else
- Cancellation is handled at every level — mid-stream state gets persisted via
state_accumulator
compact — Context Window Compaction
- When tokens exceed threshold, asks the LLM to summarize the conversation middle (everything between first and last user message)
- Creates a new session with
[first_user_msg, summary, last_user_msg_onwards...] - Atomically swaps session IDs in the database (old→archive, new→original_id)
- Updates the session in-place so all references see the new state
Session — Persistence Layer
- SQLAlchemy-backed, one row per message
Session.create()renders the Jinja2 system prompt, creates DB recordsSession.load()deserializes messages backswap_session_id()for atomic compaction swaps- Uses
coolnamefor memorable session IDs (e.g.,brave-purple-tiger-a3f2c1)
tools — ACP Tool Execution Bridge
- Routes tool calls to either ACP client capabilities (terminal, read, write) or MCP server (edit, search, fetch, etc.)
- Sends proper ACP
ToolCallStart/ToolCallProgressupdates with rich content (terminal streams, file diffs, text results) - The edit tool sends diff content to the client for display
- Handles malformed JSON from LLMs gracefully (fixes args in-place, sends error back)
configure — YAML Config System
- Reads
~/.crow/config.yamlwith${ENV_VAR}interpolation - Supports multiple LLM providers/models (provider name + base_url + API key)
- Auto-corrects common SQLite URI misconfiguration
- MCP server config with auto-path-correction for development
prompt — Multimodal Content Normalization
- Converts ACP content blocks (text, image, resource, resource_link) to OpenAI format
- Handles image blocks: base64 data,
file://URIs,http://URIs → all becomedata:URLs - Resource links resolve to file contents with line numbers
System Prompt
Jinja2 template that injects:
- Working directory + directory tree
AGENTS.mdcontent (persistent memory across sessions)- Behavioral guidelines
ReAct Loop Flowchart
flowchart TD
Start([Start react_loop]) --> Init[Initialize session and turn counter]
Init --> CheckCancel{cancel_event<br/>is set?}
CheckCancel -->|Yes| CancelStart[Log cancellation<br/>Return]
CheckCancel -->|No| SendRequest[send_request:<br/>LLM chat completion<br/>with streaming]
SendRequest --> ProcessResponse[process_response:<br/>async iterator]
subgraph Streaming[Streaming Response Processing]
ProcessResponse --> AsyncFor[async for chunk in response]
AsyncFor --> CheckUsage{has usage?}
CheckUsage -->|Yes| StoreUsage[Store final_usage]
CheckUsage -->|No| ProcessChunk
StoreUsage --> ProcessChunk
ProcessChunk[process_chunk:<br/>Parse delta] --> CheckDelta{delta type?}
CheckDelta -->|reasoning_content| AppendThinking[Append to thinking<br/>Yield: thinking, token]
CheckDelta -->|content| AppendContent[Append to content<br/>Yield: content, token]
CheckDelta -->|tool_calls| ProcessTool[Process tool call<br/>Yield: tool_call/tool_args]
AppendThinking --> AsyncFor
AppendContent --> AsyncFor
ProcessTool --> AsyncFor
end
ProcessResponse --> YieldFinal[Yield: final<br/>thinking, content, tool_call_inputs, usage]
YieldFinal --> CheckCancelStream{cancel_event<br/>is set?}
CheckCancelStream -->|Yes| CancelBeforeTool[Log cancellation<br/>add_assistant_response<br/>Return]
CheckCancelStream -->|No| CheckUsagePreTool{usage > MAX<br/>COMPACT_TOKENS?}
CheckUsagePreTool -->|Yes| Compaction[compaction:<br/>session.messages compacted]
CheckUsagePreTool -->|No| CheckTools{tool_call_inputs<br/>empty?}
Compaction --> CheckTools
CheckTools -->|Yes - No Tools| AddFinalResponse[add_assistant_response<br/>Yield: final_history<br/>Return]
CheckTools -->|No - Has Tools| ExecuteTools[execute_tool_calls:<br/>Parallel tool execution]
subgraph ToolExecution[Tool Execution Flow]
ExecuteTools --> ToolLoop{For each tool}
ToolLoop --> DetectToolType{tool type?}
DetectToolType -->|TERMINAL| execTerminal[execute_acp_terminal]
DetectToolType -->|WRITE| execWrite[execute_acp_write]
DetectToolType -->|READ| execRead[execute_acp_read]
DetectToolType -->|EDIT| execEdit[execute_acp_edit]
DetectToolType -->|Other| execGeneric[execute_acp_tool]
execTerminal --> CollectResults
execWrite --> CollectResults
execRead --> CollectResults
execEdit --> CollectResults
execGeneric --> CollectResults
CollectResults[Collect tool_results]
end
CollectResults --> CheckCancelAfter{cancel_event<br/>is set?}
CheckCancelAfter -->|Yes| CancelAfterTool{tool_results<br/>exist?}
CancelAfterTool -->|Yes| AddBothResponses[add_assistant_response<br/>add_tool_response<br/>Return]
CancelAfterTool -->|No| ReturnEmpty[Return]
CheckCancelAfter -->|No| AddAssistant[add_assistant_response<br/>thinking, content, tool_call_inputs]
AddAssistant --> AddTool[add_tool_response<br/>tool_results]
AddTool --> LoopBack
LoopBack --> IncrementTurn[turn += 1]
IncrementTurn --> CheckCancel
License
README copy captured when this server was indexed; it may lag the repository. The original is authoritative.