regcode 0.8.1__tar.gz → 0.10.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {regcode-0.8.1 → regcode-0.10.0}/.gitignore +3 -1
- {regcode-0.8.1 → regcode-0.10.0}/GOALS.md +50 -6
- {regcode-0.8.1 → regcode-0.10.0}/PKG-INFO +2 -1
- regcode-0.10.0/config.yaml.template +56 -0
- {regcode-0.8.1 → regcode-0.10.0}/pyproject.toml +2 -1
- {regcode-0.8.1 → regcode-0.10.0}/regcode/__init__.py +1 -1
- {regcode-0.8.1 → regcode-0.10.0}/regcode/cli.py +84 -1
- {regcode-0.8.1 → regcode-0.10.0}/regcode/config.py +36 -0
- {regcode-0.8.1 → regcode-0.10.0}/regcode/main.py +517 -500
- regcode-0.10.0/regcode/mcp_client.py +595 -0
- {regcode-0.8.1 → regcode-0.10.0}/regcode/tui.py +81 -52
- {regcode-0.8.1 → regcode-0.10.0}/tests/test_cli.py +1 -0
- regcode-0.10.0/tests/test_mcp.py +376 -0
- regcode-0.10.0/tests/test_mcp_status.py +188 -0
- {regcode-0.8.1 → regcode-0.10.0}/uv.lock +109 -10
- regcode-0.8.1/config.yaml.template +0 -16
- {regcode-0.8.1 → regcode-0.10.0}/.gitlab-ci.yml +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/.python-version +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/.qwen/settings.json +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/AGENTS.md +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/README.md +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/main.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/regcode/clipboard.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/regcode/conversation_manager.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/regcode/dependencies.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/regcode/monty_sandbox.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/regcode/paste.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/regcode/permissions.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/regcode/sandbox.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/regcode/tools/__init__.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/regcode/tools/base.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/regcode/tools/builtins.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/regcode/tools/registry.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/regcode/tools/review_notes.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/regcode/tools/web_crawl.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/regcode/tools/web_search.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/regcode/tracing.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/tests/__init__.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/tests/conftest.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/tests/test_api.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/tests/test_clipboard.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/tests/test_config.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/tests/test_context_manager.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/tests/test_dependencies.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/tests/test_main.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/tests/test_monty_sandbox.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/tests/test_osv_cve.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/tests/test_provider_extra.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/tests/test_review_notes.py +0 -0
- {regcode-0.8.1 → regcode-0.10.0}/tests/test_web_search.py +0 -0
|
@@ -505,6 +505,45 @@ All phases complete. 50/50 tests passing. Lint clean.
|
|
|
505
505
|
|
|
506
506
|
- ✅ WebSearchTool: connects to SearXNG, defaults to https://search.regmi.dev, no extra deps needed
|
|
507
507
|
|
|
508
|
+
## MCP Support (2026-07-05)
|
|
509
|
+
|
|
510
|
+
- ✅ `mcp` package added to `pyproject.toml` dependencies
|
|
511
|
+
- ✅ `McpServerConfig` Pydantic model in `regcode/config.py` — supports both stdio (command+args+env) and HTTP (url+headers) transports, with per-tool timeout
|
|
512
|
+
- ✅ `regcode/mcp_client.py` — MCP client with two transport strategies:
|
|
513
|
+
• **stdio** — uses MCP library's `stdio_client` for command-based servers (npx, uvx)
|
|
514
|
+
• **HTTP blocking JSON-RPC** — uses `httpx.Client` directly for servers that return 200 OK with JSON body (not 202 + SSE). Supports session-ID propagation via `mcp-session-id` header, credential injection via custom headers.
|
|
515
|
+
• Discovers tools via `tools/list` JSON-RPC call, registers them into Agent's `ToolRegistry` with `mcp_{server}_{tool}` naming
|
|
516
|
+
• Credential redaction in error messages
|
|
517
|
+
- ✅ `Agent.__init__` calls `_discover_mcp_tools()` at startup — reads `mcp_servers` from config, connects to each server, registers tools. Gracefully skips if `mcp` package not installed or connection fails.
|
|
518
|
+
- ✅ `Agent.reload_mcp()` method — hot-reloads MCP connections (closes old, discovers new) without restart
|
|
519
|
+
- ✅ `Agent._mcp_status()` — returns formatted text showing all MCP servers, their status, and discovered tools
|
|
520
|
+
- ✅ `Agent.cleanup()` includes MCP session close
|
|
521
|
+
- ✅ `McpClient.get_server_info()` — returns introspection data for all configured servers (name, transport, connected status, error, tool list with params)
|
|
522
|
+
- ✅ `/mcp` slash command in TUI — lists all configured MCP servers, shows connection status, enumerates discovered tools with descriptions and parameters
|
|
523
|
+
- ✅ CLI `configure` command generates `mcp_servers` section in config.yaml (interactive prompt for stdio/HTTP servers)
|
|
524
|
+
- ✅ `config.yaml.template` includes commented MCP server examples for stdio (npx, uvx) and HTTP transport
|
|
525
|
+
- ✅ 31 MCP config tests: `TestMcpServerConfig`, `TestConfigMcpServers`, `TestSanitizeName`, `TestRedactCredentials`, `TestMcpClientAgentIntegration`, `TestMcpClientConnectAll`
|
|
526
|
+
- ✅ 7 MCP status tests: `test_get_server_info_*`, `test_mcp_status_format*`
|
|
527
|
+
- ✅ All 231 tests pass, lint clean
|
|
528
|
+
|
|
529
|
+
Example config.yaml MCP section:
|
|
530
|
+
```yaml
|
|
531
|
+
mcp_servers:
|
|
532
|
+
github:
|
|
533
|
+
command: npx
|
|
534
|
+
args: ["-y", "@modelcontextprotocol/server-github"]
|
|
535
|
+
env:
|
|
536
|
+
GITHUB_TOKEN: ghp_xxxxx
|
|
537
|
+
timeout: 60
|
|
538
|
+
filesystem:
|
|
539
|
+
command: uvx
|
|
540
|
+
args: ["mcp-server-filesystem", "/tmp"]
|
|
541
|
+
remote_api:
|
|
542
|
+
url: https://mcp.example.com/v1/mcp
|
|
543
|
+
headers:
|
|
544
|
+
Authorization: Bearer sk-xxxxx
|
|
545
|
+
```
|
|
546
|
+
|
|
508
547
|
## Completed (2026-06-30 — TUI fixes)
|
|
509
548
|
|
|
510
549
|
1. ✅ AGENTS.md injection — Agent now reads AGENTS.md/agents.md from CWD or allowed directory and injects it into system prompt (both `_build_system_prompt` and `_build_system_prompt_no_tools`). Appended after base system_prompt, before tool descriptions.
|
|
@@ -530,10 +569,15 @@ All phases complete. 50/50 tests passing. Lint clean.
|
|
|
530
569
|
|
|
531
570
|
## Completed (2026-06-30 — TUI rewrite)
|
|
532
571
|
|
|
533
|
-
|
|
572
|
+
## Completed (2026-07-05)
|
|
534
573
|
|
|
535
|
-
1. ✅
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
574
|
+
1. ✅ **Agent loop rewrite** — `Agent.chat()` completely rewritten following `debug.ipynb` pattern. Key changes:
|
|
575
|
+
- Extracted `_chat_loop()` for non-streaming mode, `_stream()` for streaming — both use shared `_execute_tool_calls()` helper (eliminating ~300 lines of duplicated tool execution code).
|
|
576
|
+
- Added `on_message` callback parameter: called after every LLM call with the assistant's text content BEFORE tool results are shown. This fixes the broken chain where "Let me search" was never displayed — the user now sees it in real-time as tools execute.
|
|
577
|
+
- Fixed `_stream()`: removed `full_text = ""` reset at each iteration (was wiping previously accumulated text). Now `full_text` accumulates across ALL iterations so the user sees the complete conversation.
|
|
578
|
+
- Fixed `_chat_loop` loop bound: was using `self.config.tool_budget` (default 20) instead of `max_tool_calls` (default 10), causing excessive LLM calls. Now properly bounded by `max_tool_calls`.
|
|
579
|
+
- When `finish_reason == "stop"` or text + no tools, loop breaks immediately — no more dead-end loop iterations.
|
|
580
|
+
|
|
581
|
+
2. ✅ **TUI integration** — `_run_agent_sync()` and `_run_agent_multimodal()` now pass `on_message` callback to `agent.chat()`. The callback inserts each assistant text response directly into the chat area via `call_from_thread(self._insert_rich, text)`, so the user sees intermediate thinking ("Let me search the internet...") BEFORE tool execution markers appear. This makes the agent feel alive and responsive instead of silent during multi-tool chains.
|
|
582
|
+
|
|
583
|
+
3. ✅ **All 193 tests pass, lint clean.**
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: regcode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: Minimalistic coding agent with Python API and CLI
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Requires-Dist: boto3>=1.43.40
|
|
7
7
|
Requires-Dist: click>=8.0
|
|
8
8
|
Requires-Dist: crawl4ai>=0.4.0
|
|
9
9
|
Requires-Dist: litellm>=1.83.17
|
|
10
|
+
Requires-Dist: mcp>=1.0.0
|
|
10
11
|
Requires-Dist: mlflow>=3.14.0
|
|
11
12
|
Requires-Dist: pathspec>=1.1.1
|
|
12
13
|
Requires-Dist: pydantic-monty>=0.0.18
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
agent:
|
|
2
|
+
# Context window management
|
|
3
|
+
enable_compaction: true
|
|
4
|
+
compaction_threshold: 0.8
|
|
5
|
+
context_window: 128000
|
|
6
|
+
|
|
7
|
+
provider:
|
|
8
|
+
api_key: "${OPENAI_API_KEY}"
|
|
9
|
+
model: "openai/gpt-4o"
|
|
10
|
+
max_tokens: 4096
|
|
11
|
+
temperature: 0.7
|
|
12
|
+
|
|
13
|
+
# ── MCP Servers (optional) ──────────────────────────────────────
|
|
14
|
+
#
|
|
15
|
+
# Add MCP servers to extend the agent with external tools.
|
|
16
|
+
# The agent connects on startup, discovers available tools,
|
|
17
|
+
# and registers them as `mcp_{server}_{tool_name}`.
|
|
18
|
+
#
|
|
19
|
+
# Two transport types are supported:
|
|
20
|
+
#
|
|
21
|
+
# 1. Stdio (command-based, e.g. npx, uvx):
|
|
22
|
+
# command — executable to run
|
|
23
|
+
# args — arguments passed to the command
|
|
24
|
+
# env — environment variables (only these are passed,
|
|
25
|
+
# NOT the full shell environment)
|
|
26
|
+
#
|
|
27
|
+
# 2. HTTP (StreamableHTTP URL):
|
|
28
|
+
# url — server endpoint
|
|
29
|
+
# headers — HTTP headers sent with each request
|
|
30
|
+
#
|
|
31
|
+
# Example:
|
|
32
|
+
|
|
33
|
+
mcp_servers:
|
|
34
|
+
# GitHub server — uses npx to run the official MCP server
|
|
35
|
+
#github:
|
|
36
|
+
# command: "npx"
|
|
37
|
+
# args: ["-y", "@modelcontextprotocol/server-github"]
|
|
38
|
+
# env:
|
|
39
|
+
# GITHUB_PERSONAL_ACCESS_TOKEN: "${GITHUB_TOKEN}"
|
|
40
|
+
# timeout: 60
|
|
41
|
+
|
|
42
|
+
# Filesystem server — lets the agent read/write arbitrary files
|
|
43
|
+
# filesystem:
|
|
44
|
+
# command: "uvx"
|
|
45
|
+
# args: ["mcp-server-filesystem", "/tmp"]
|
|
46
|
+
|
|
47
|
+
# Remote HTTP server
|
|
48
|
+
# remote_api:
|
|
49
|
+
# url: "https://mcp.example.com/v1/mcp"
|
|
50
|
+
# headers:
|
|
51
|
+
# Authorization: "Bearer ${MCP_API_KEY}"
|
|
52
|
+
|
|
53
|
+
tools:
|
|
54
|
+
code_review: true
|
|
55
|
+
security_scan: false
|
|
56
|
+
sandbox: false
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "regcode"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.10.0"
|
|
4
4
|
description = "Minimalistic coding agent with Python API and CLI"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -18,6 +18,7 @@ dependencies = [
|
|
|
18
18
|
"pathspec>=1.1.1",
|
|
19
19
|
"pyperclip>=1.9.0",
|
|
20
20
|
"boto3>=1.43.40",
|
|
21
|
+
"mcp>=1.0.0",
|
|
21
22
|
]
|
|
22
23
|
|
|
23
24
|
[project.scripts]
|
|
@@ -139,8 +139,84 @@ def configure(output_path):
|
|
|
139
139
|
else:
|
|
140
140
|
click.echo(" Using default provider settings.")
|
|
141
141
|
|
|
142
|
+
# --- MCP servers config ---
|
|
143
|
+
yes = _confirm("Configure MCP servers? (optional)")
|
|
144
|
+
if yes:
|
|
145
|
+
mcp_section: dict[str, dict] = {}
|
|
146
|
+
while True:
|
|
147
|
+
server_name = click.prompt(
|
|
148
|
+
"Server name (e.g. github, filesystem)",
|
|
149
|
+
default="",
|
|
150
|
+
)
|
|
151
|
+
if not server_name:
|
|
152
|
+
break
|
|
153
|
+
|
|
154
|
+
transport = click.prompt(
|
|
155
|
+
"Transport type",
|
|
156
|
+
type=click.Choice(["stdio", "http"]),
|
|
157
|
+
default="stdio",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if transport == "stdio":
|
|
161
|
+
command = click.prompt("Command (e.g. npx, uvx)")
|
|
162
|
+
args_str = click.prompt(
|
|
163
|
+
"Arguments (comma-separated, e.g. -y,package-name)",
|
|
164
|
+
default="",
|
|
165
|
+
)
|
|
166
|
+
args = [
|
|
167
|
+
a.strip()
|
|
168
|
+
for a in args_str.split(",")
|
|
169
|
+
if a.strip()
|
|
170
|
+
] if args_str else []
|
|
171
|
+
|
|
172
|
+
env_str = click.prompt(
|
|
173
|
+
"Environment variables (KEY=VALUE lines, empty to skip)",
|
|
174
|
+
default="",
|
|
175
|
+
)
|
|
176
|
+
env = {}
|
|
177
|
+
for line in env_str.splitlines():
|
|
178
|
+
line = line.strip()
|
|
179
|
+
if line and "=" in line:
|
|
180
|
+
k, v = line.split("=", 1)
|
|
181
|
+
env[k.strip()] = v.strip()
|
|
182
|
+
|
|
183
|
+
mcp_section[server_name] = {
|
|
184
|
+
"command": command,
|
|
185
|
+
"args": args,
|
|
186
|
+
"env": env if env else None,
|
|
187
|
+
}
|
|
188
|
+
else:
|
|
189
|
+
url = click.prompt("Server URL (e.g. https://example.com/mcp)")
|
|
190
|
+
headers_str = click.prompt(
|
|
191
|
+
"Headers (KEY=VALUE lines, empty to skip)",
|
|
192
|
+
default="",
|
|
193
|
+
)
|
|
194
|
+
headers = {}
|
|
195
|
+
for line in headers_str.splitlines():
|
|
196
|
+
line = line.strip()
|
|
197
|
+
if line and "=" in line:
|
|
198
|
+
k, v = line.split("=", 1)
|
|
199
|
+
headers[k.strip()] = v.strip()
|
|
200
|
+
|
|
201
|
+
mcp_section[server_name] = {
|
|
202
|
+
"url": url,
|
|
203
|
+
"headers": headers if headers else None,
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if mcp_section:
|
|
207
|
+
from regcode.config import McpServerConfig
|
|
208
|
+
|
|
209
|
+
config.mcp_servers = {
|
|
210
|
+
name: McpServerConfig.model_validate(
|
|
211
|
+
{k: v for k, v in cfg.items() if v is not None}
|
|
212
|
+
)
|
|
213
|
+
for name, cfg in mcp_section.items()
|
|
214
|
+
}
|
|
215
|
+
else:
|
|
216
|
+
click.echo(" Using default MCP settings (no servers).")
|
|
217
|
+
|
|
142
218
|
# --- Write config.yaml ---
|
|
143
|
-
data = {
|
|
219
|
+
data: dict = {
|
|
144
220
|
"agent": {
|
|
145
221
|
"context_window": config.agent.context_window,
|
|
146
222
|
"compaction_threshold": config.agent.compaction_threshold,
|
|
@@ -157,6 +233,13 @@ def configure(output_path):
|
|
|
157
233
|
"tool_budget": config.tool_budget,
|
|
158
234
|
}
|
|
159
235
|
|
|
236
|
+
# Add MCP servers section if configured
|
|
237
|
+
if hasattr(config, "mcp_servers") and config.mcp_servers:
|
|
238
|
+
data["mcp_servers"] = {
|
|
239
|
+
name: cfg.model_dump(exclude_none=True)
|
|
240
|
+
for name, cfg in config.mcp_servers.items()
|
|
241
|
+
}
|
|
242
|
+
|
|
160
243
|
with open(output_path, "w") as f:
|
|
161
244
|
yaml.dump(data, f, default_flow_style=False)
|
|
162
245
|
click.echo(f"\nconfig.yaml written to {output_path}")
|
|
@@ -38,11 +38,46 @@ default_system_prompt = dedent(
|
|
|
38
38
|
Start directly with your review content. Do not address user or
|
|
39
39
|
use fillers like "Now I have enough information to provide a review." or
|
|
40
40
|
"Here is my review:"
|
|
41
|
+
|
|
42
|
+
## Code implementation
|
|
43
|
+
If you are asked to implement code, do what a responsible developer would do:
|
|
44
|
+
- researh what library versions are used
|
|
45
|
+
- if library versions are newer than your knowledge cutoff, research first.
|
|
46
|
+
- write code
|
|
47
|
+
- check if tests exist. Always write tests for your code if they don't exist.
|
|
48
|
+
- Make sure to comply to users coding pattern and style.
|
|
41
49
|
"""
|
|
42
50
|
)
|
|
43
51
|
|
|
44
52
|
logger = logging.getLogger(__name__)
|
|
45
53
|
|
|
54
|
+
class McpServerConfig(BaseModel):
|
|
55
|
+
"""Configuration for a single MCP server.
|
|
56
|
+
|
|
57
|
+
Supports two transport types:
|
|
58
|
+
- Stdio: command-based (npx, uvx, or any executable)
|
|
59
|
+
- HTTP: URL-based (StreamableHTTP)
|
|
60
|
+
|
|
61
|
+
Exactly one of ``command`` or ``url`` must be provided.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
model_config = ConfigDict(extra="allow")
|
|
65
|
+
|
|
66
|
+
# Stdio transport
|
|
67
|
+
command: str | None = None
|
|
68
|
+
args: list[str] = []
|
|
69
|
+
env: dict[str, str] = {}
|
|
70
|
+
connect_timeout: int = 60
|
|
71
|
+
|
|
72
|
+
# HTTP transport
|
|
73
|
+
url: str | None = None
|
|
74
|
+
headers: dict[str, str] = {}
|
|
75
|
+
connect_timeout_http: int = 60
|
|
76
|
+
|
|
77
|
+
# Per-tool-call timeout
|
|
78
|
+
timeout: int = 120
|
|
79
|
+
|
|
80
|
+
|
|
46
81
|
class ToolSetConfig(BaseModel):
|
|
47
82
|
"""Configuration for a set of tools."""
|
|
48
83
|
|
|
@@ -126,6 +161,7 @@ class Config(BaseModel):
|
|
|
126
161
|
permissions: list[AgentPermission] = [AgentPermission.READ, AgentPermission.EXECUTE]
|
|
127
162
|
toolset_config: ToolSetConfig = ToolSetConfig()
|
|
128
163
|
debug_mode: bool = False
|
|
164
|
+
mcp_servers: dict[str, McpServerConfig] = {}
|
|
129
165
|
|
|
130
166
|
@classmethod
|
|
131
167
|
def load(cls, path: str | None = None) -> "Config":
|