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.
Files changed (50) hide show
  1. {regcode-0.8.1 → regcode-0.10.0}/.gitignore +3 -1
  2. {regcode-0.8.1 → regcode-0.10.0}/GOALS.md +50 -6
  3. {regcode-0.8.1 → regcode-0.10.0}/PKG-INFO +2 -1
  4. regcode-0.10.0/config.yaml.template +56 -0
  5. {regcode-0.8.1 → regcode-0.10.0}/pyproject.toml +2 -1
  6. {regcode-0.8.1 → regcode-0.10.0}/regcode/__init__.py +1 -1
  7. {regcode-0.8.1 → regcode-0.10.0}/regcode/cli.py +84 -1
  8. {regcode-0.8.1 → regcode-0.10.0}/regcode/config.py +36 -0
  9. {regcode-0.8.1 → regcode-0.10.0}/regcode/main.py +517 -500
  10. regcode-0.10.0/regcode/mcp_client.py +595 -0
  11. {regcode-0.8.1 → regcode-0.10.0}/regcode/tui.py +81 -52
  12. {regcode-0.8.1 → regcode-0.10.0}/tests/test_cli.py +1 -0
  13. regcode-0.10.0/tests/test_mcp.py +376 -0
  14. regcode-0.10.0/tests/test_mcp_status.py +188 -0
  15. {regcode-0.8.1 → regcode-0.10.0}/uv.lock +109 -10
  16. regcode-0.8.1/config.yaml.template +0 -16
  17. {regcode-0.8.1 → regcode-0.10.0}/.gitlab-ci.yml +0 -0
  18. {regcode-0.8.1 → regcode-0.10.0}/.python-version +0 -0
  19. {regcode-0.8.1 → regcode-0.10.0}/.qwen/settings.json +0 -0
  20. {regcode-0.8.1 → regcode-0.10.0}/AGENTS.md +0 -0
  21. {regcode-0.8.1 → regcode-0.10.0}/README.md +0 -0
  22. {regcode-0.8.1 → regcode-0.10.0}/main.py +0 -0
  23. {regcode-0.8.1 → regcode-0.10.0}/regcode/clipboard.py +0 -0
  24. {regcode-0.8.1 → regcode-0.10.0}/regcode/conversation_manager.py +0 -0
  25. {regcode-0.8.1 → regcode-0.10.0}/regcode/dependencies.py +0 -0
  26. {regcode-0.8.1 → regcode-0.10.0}/regcode/monty_sandbox.py +0 -0
  27. {regcode-0.8.1 → regcode-0.10.0}/regcode/paste.py +0 -0
  28. {regcode-0.8.1 → regcode-0.10.0}/regcode/permissions.py +0 -0
  29. {regcode-0.8.1 → regcode-0.10.0}/regcode/sandbox.py +0 -0
  30. {regcode-0.8.1 → regcode-0.10.0}/regcode/tools/__init__.py +0 -0
  31. {regcode-0.8.1 → regcode-0.10.0}/regcode/tools/base.py +0 -0
  32. {regcode-0.8.1 → regcode-0.10.0}/regcode/tools/builtins.py +0 -0
  33. {regcode-0.8.1 → regcode-0.10.0}/regcode/tools/registry.py +0 -0
  34. {regcode-0.8.1 → regcode-0.10.0}/regcode/tools/review_notes.py +0 -0
  35. {regcode-0.8.1 → regcode-0.10.0}/regcode/tools/web_crawl.py +0 -0
  36. {regcode-0.8.1 → regcode-0.10.0}/regcode/tools/web_search.py +0 -0
  37. {regcode-0.8.1 → regcode-0.10.0}/regcode/tracing.py +0 -0
  38. {regcode-0.8.1 → regcode-0.10.0}/tests/__init__.py +0 -0
  39. {regcode-0.8.1 → regcode-0.10.0}/tests/conftest.py +0 -0
  40. {regcode-0.8.1 → regcode-0.10.0}/tests/test_api.py +0 -0
  41. {regcode-0.8.1 → regcode-0.10.0}/tests/test_clipboard.py +0 -0
  42. {regcode-0.8.1 → regcode-0.10.0}/tests/test_config.py +0 -0
  43. {regcode-0.8.1 → regcode-0.10.0}/tests/test_context_manager.py +0 -0
  44. {regcode-0.8.1 → regcode-0.10.0}/tests/test_dependencies.py +0 -0
  45. {regcode-0.8.1 → regcode-0.10.0}/tests/test_main.py +0 -0
  46. {regcode-0.8.1 → regcode-0.10.0}/tests/test_monty_sandbox.py +0 -0
  47. {regcode-0.8.1 → regcode-0.10.0}/tests/test_osv_cve.py +0 -0
  48. {regcode-0.8.1 → regcode-0.10.0}/tests/test_provider_extra.py +0 -0
  49. {regcode-0.8.1 → regcode-0.10.0}/tests/test_review_notes.py +0 -0
  50. {regcode-0.8.1 → regcode-0.10.0}/tests/test_web_search.py +0 -0
@@ -11,4 +11,6 @@ wheels/
11
11
  config.yaml
12
12
  review.py
13
13
  *.config.yaml
14
- .env
14
+ .env
15
+ debug*.*
16
+ llm_calls*.json
@@ -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
- Complete rewrite of `tui.py` from scratch using Textual's `RichLog` widget instead of `TextArea`:
572
+ ## Completed (2026-07-05)
534
573
 
535
- 1. ✅ **`RichLog` instead of `TextArea`** — RichLog natively renders Rich markup (`[bold]`, `[dim]`, etc.) so no fragile Rich→ANSI conversion is needed. Users see properly colored, styled text. RichLog also supports scrollback selection/copy via terminal features.
536
- 2. ✅ **Clean Catppuccin Mocha dark theme** — uses the Catppuccin color palette (`#1e1e2e` background, `#cdd6f4` text, `#313244` surfaces, `#89b4fa` accents) for a modern, polished look.
537
- 3. ✅ **Proper three-zone layout** — status bar (version | model | cost | tokens), scrollable chat history, bottom input bar with progress indicator. No raw markup tags visible.
538
- 4. ✅ **Same backward-compatible API** — `ChatUI` facade, `ChatApp` class, `_ColorFormatter`, all callback methods (`handle_status`, `handle_result`, `handle_cost`, `print_text_chunk`, etc.) preserved exactly as before for CLI integration.
539
- 5. ✅ **All 164 tests pass**, lint clean.
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.8.1
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.8.1"
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]
@@ -2,4 +2,4 @@ from regcode.config import Config
2
2
  from regcode.main import Agent, AgentStatus, ToolCall
3
3
 
4
4
  __all__ = ["Agent", "Config", "AgentStatus", "ToolCall"]
5
- __version__ = "0.8.1"
5
+ __version__ = "0.10.0"
@@ -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":