mycode-aiagent 0.4.0__tar.gz → 0.4.2__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.
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/PKG-INFO +38 -13
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/README.md +37 -12
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/my_code/__init__.py +1 -2
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/my_code/backends/__init__.py +3 -7
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/my_code/cli.py +19 -9
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/my_code/mcp_client.py +26 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/mycode_aiagent.egg-info/PKG-INFO +38 -13
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/mycode_aiagent.egg-info/SOURCES.txt +2 -2
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/pyproject.toml +1 -1
- mycode_aiagent-0.4.2/tests/test_server.py +138 -0
- mycode_aiagent-0.4.0/my_code/backends/ricky_backend.py +0 -15
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/my_code/__main__.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/my_code/analyzer.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/my_code/backends/base.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/my_code/backends/claude_backend.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/my_code/backends/mcp_backend.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/my_code/backends/openai_backend.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/my_code/generator.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/my_code/server.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/my_code/utils/__init__.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/my_code/utils/prompts.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/mycode_aiagent.egg-info/dependency_links.txt +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/mycode_aiagent.egg-info/entry_points.txt +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/mycode_aiagent.egg-info/requires.txt +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/mycode_aiagent.egg-info/top_level.txt +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/setup.cfg +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.2}/tests/test_library.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mycode-aiagent
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Style-aware code generation — analyze any codebase and generate new code that matches its style
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/RyanAbbottData/MyCode
|
|
@@ -90,14 +90,15 @@ MyCode delegates inference to a pluggable backend. Choose one based on what you
|
|
|
90
90
|
|
|
91
91
|
| Backend | Flag | Requirement |
|
|
92
92
|
|---|---|---|
|
|
93
|
-
|
|
|
94
|
-
| Anthropic Claude | `--backend claude` | `ANTHROPIC_API_KEY` env var or `--api-key` |
|
|
93
|
+
| Anthropic Claude | `--backend claude` (default) | `ANTHROPIC_API_KEY` env var or `--api-key` |
|
|
95
94
|
| OpenAI | `--backend openai` | `OPENAI_API_KEY` env var or `--api-key` |
|
|
96
|
-
|
|
|
95
|
+
| Any MCP server | `--backend mcp` | An MCP server at `--mcp-url` (MyCode server or local LLM wrapper) |
|
|
96
|
+
|
|
97
|
+
When `--backend mcp` is used with `analyze` or `generate`, the CLI delegates the entire operation to the running MyCode server — it calls the server's `analyze_codebase` or `generate_code` tool directly instead of running the pipeline locally. This is the recommended way to use MyCode in a multi-project or agentic setup.
|
|
97
98
|
|
|
98
99
|
### Setting up a local LLM
|
|
99
100
|
|
|
100
|
-
The `
|
|
101
|
+
The `mcp` backend can connect to any MCP server, including a local LLM wrapper. The server must expose two tools: one for code generation and one for analysis (tool name must contain `"analyze"`). Here is a recommended setup using [llama.cpp](https://github.com/ggerganov/llama.cpp):
|
|
101
102
|
|
|
102
103
|
**1. Download a model**
|
|
103
104
|
|
|
@@ -124,7 +125,7 @@ python -m llama_cpp.server \
|
|
|
124
125
|
|
|
125
126
|
**3. Wrap it with an MCP server**
|
|
126
127
|
|
|
127
|
-
The `
|
|
128
|
+
The `mcp` backend communicates over MCP, not directly with the llama.cpp HTTP API. You need a thin MCP wrapper that exposes two tools:
|
|
128
129
|
- A **code generation tool** (name must not contain `"analyze"`)
|
|
129
130
|
- An **analysis tool** (name must contain `"analyze"`)
|
|
130
131
|
|
|
@@ -166,13 +167,13 @@ python llm_mcp_server.py
|
|
|
166
167
|
**4. Point MyCode at it**
|
|
167
168
|
|
|
168
169
|
```bash
|
|
169
|
-
my-code --backend
|
|
170
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze .
|
|
170
171
|
```
|
|
171
172
|
|
|
172
173
|
Or set a custom URL if your server runs on a different port:
|
|
173
174
|
|
|
174
175
|
```bash
|
|
175
|
-
my-code --backend
|
|
176
|
+
my-code --backend mcp --mcp-url http://localhost:9000/mcp analyze .
|
|
176
177
|
```
|
|
177
178
|
|
|
178
179
|
---
|
|
@@ -203,6 +204,11 @@ The profile is saved to `style_profile.json` by default. Specify a different pat
|
|
|
203
204
|
my-code analyze ./path/to/codebase --profile ./profiles/my_team.json
|
|
204
205
|
```
|
|
205
206
|
|
|
207
|
+
Delegating to a running MyCode server:
|
|
208
|
+
```bash
|
|
209
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./path/to/codebase
|
|
210
|
+
```
|
|
211
|
+
|
|
206
212
|
### Step 2 — Generate code
|
|
207
213
|
|
|
208
214
|
```bash
|
|
@@ -220,6 +226,9 @@ my-code --backend claude generate "write a binary search function"
|
|
|
220
226
|
|
|
221
227
|
# Override the model
|
|
222
228
|
my-code --backend claude --model claude-sonnet-4-6 generate "write a rate limiter"
|
|
229
|
+
|
|
230
|
+
# Delegate to a running MyCode server
|
|
231
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a rate limiter"
|
|
223
232
|
```
|
|
224
233
|
|
|
225
234
|
---
|
|
@@ -230,11 +239,10 @@ my-code --backend claude --model claude-sonnet-4-6 generate "write a rate limite
|
|
|
230
239
|
my-code [OPTIONS] COMMAND
|
|
231
240
|
|
|
232
241
|
Options:
|
|
233
|
-
--backend {
|
|
242
|
+
--backend {claude,openai,mcp} AI backend to use (default: claude)
|
|
234
243
|
--api-key TEXT API key for claude/openai backends
|
|
235
244
|
--model TEXT Override the default model
|
|
236
|
-
--
|
|
237
|
-
--mcp-url TEXT Custom MCP server URL (default: http://localhost:8001/mcp)
|
|
245
|
+
--mcp-url TEXT MCP server URL (default: http://localhost:8001/mcp)
|
|
238
246
|
--profile TEXT Path to style profile JSON (default: style_profile.json)
|
|
239
247
|
|
|
240
248
|
Commands:
|
|
@@ -364,6 +372,24 @@ my-code stop --pid-file mycode-8080.pid
|
|
|
364
372
|
my-code stop --pid-file mycode-8081.pid
|
|
365
373
|
```
|
|
366
374
|
|
|
375
|
+
### Using the server from the CLI
|
|
376
|
+
|
|
377
|
+
Start a MyCode server as a daemon, then point `analyze` and `generate` at it with `--backend mcp`. The CLI calls the server's tools directly — the server handles all analysis and generation using whichever LLM it was started with.
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
# Start the server (uses a local LLM at port 8001 as its brain)
|
|
381
|
+
my-code --backend mcp --mcp-url http://localhost:8001/mcp serve --daemon --port 8000
|
|
382
|
+
|
|
383
|
+
# Analyze a codebase via the server
|
|
384
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./my_project
|
|
385
|
+
|
|
386
|
+
# Generate code via the server (loads style_profile.json locally and sends it)
|
|
387
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a retry decorator"
|
|
388
|
+
|
|
389
|
+
# Stop the server
|
|
390
|
+
my-code stop
|
|
391
|
+
```
|
|
392
|
+
|
|
367
393
|
### Connecting from an MCP consumer
|
|
368
394
|
|
|
369
395
|
Add the printed snippet to your consumer's MCP config file (e.g. `.mcp.json`):
|
|
@@ -443,12 +469,11 @@ Both test suites use a `MockBackend` — no live AI backend required.
|
|
|
443
469
|
my_code/
|
|
444
470
|
├── analyzer.py # StyleAnalyzer — scans files, builds style profile
|
|
445
471
|
├── generator.py # generate_code() — formats prompt and calls backend
|
|
446
|
-
├── mcp_client.py #
|
|
472
|
+
├── mcp_client.py # MCPClient (raw LLM wrapper) + MyCodeClient (server delegation)
|
|
447
473
|
├── server.py # MCPServer — exposes analyze/generate as MCP tools
|
|
448
474
|
├── cli.py # CLI entry point (my-code command)
|
|
449
475
|
├── backends/
|
|
450
476
|
│ ├── base.py # AIBackend abstract base class
|
|
451
|
-
│ ├── ricky_backend.py # Local LLM backend (connects via MCP)
|
|
452
477
|
│ ├── claude_backend.py
|
|
453
478
|
│ ├── openai_backend.py
|
|
454
479
|
│ └── mcp_backend.py # Generic MCP server backend
|
|
@@ -61,14 +61,15 @@ MyCode delegates inference to a pluggable backend. Choose one based on what you
|
|
|
61
61
|
|
|
62
62
|
| Backend | Flag | Requirement |
|
|
63
63
|
|---|---|---|
|
|
64
|
-
|
|
|
65
|
-
| Anthropic Claude | `--backend claude` | `ANTHROPIC_API_KEY` env var or `--api-key` |
|
|
64
|
+
| Anthropic Claude | `--backend claude` (default) | `ANTHROPIC_API_KEY` env var or `--api-key` |
|
|
66
65
|
| OpenAI | `--backend openai` | `OPENAI_API_KEY` env var or `--api-key` |
|
|
67
|
-
|
|
|
66
|
+
| Any MCP server | `--backend mcp` | An MCP server at `--mcp-url` (MyCode server or local LLM wrapper) |
|
|
67
|
+
|
|
68
|
+
When `--backend mcp` is used with `analyze` or `generate`, the CLI delegates the entire operation to the running MyCode server — it calls the server's `analyze_codebase` or `generate_code` tool directly instead of running the pipeline locally. This is the recommended way to use MyCode in a multi-project or agentic setup.
|
|
68
69
|
|
|
69
70
|
### Setting up a local LLM
|
|
70
71
|
|
|
71
|
-
The `
|
|
72
|
+
The `mcp` backend can connect to any MCP server, including a local LLM wrapper. The server must expose two tools: one for code generation and one for analysis (tool name must contain `"analyze"`). Here is a recommended setup using [llama.cpp](https://github.com/ggerganov/llama.cpp):
|
|
72
73
|
|
|
73
74
|
**1. Download a model**
|
|
74
75
|
|
|
@@ -95,7 +96,7 @@ python -m llama_cpp.server \
|
|
|
95
96
|
|
|
96
97
|
**3. Wrap it with an MCP server**
|
|
97
98
|
|
|
98
|
-
The `
|
|
99
|
+
The `mcp` backend communicates over MCP, not directly with the llama.cpp HTTP API. You need a thin MCP wrapper that exposes two tools:
|
|
99
100
|
- A **code generation tool** (name must not contain `"analyze"`)
|
|
100
101
|
- An **analysis tool** (name must contain `"analyze"`)
|
|
101
102
|
|
|
@@ -137,13 +138,13 @@ python llm_mcp_server.py
|
|
|
137
138
|
**4. Point MyCode at it**
|
|
138
139
|
|
|
139
140
|
```bash
|
|
140
|
-
my-code --backend
|
|
141
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze .
|
|
141
142
|
```
|
|
142
143
|
|
|
143
144
|
Or set a custom URL if your server runs on a different port:
|
|
144
145
|
|
|
145
146
|
```bash
|
|
146
|
-
my-code --backend
|
|
147
|
+
my-code --backend mcp --mcp-url http://localhost:9000/mcp analyze .
|
|
147
148
|
```
|
|
148
149
|
|
|
149
150
|
---
|
|
@@ -174,6 +175,11 @@ The profile is saved to `style_profile.json` by default. Specify a different pat
|
|
|
174
175
|
my-code analyze ./path/to/codebase --profile ./profiles/my_team.json
|
|
175
176
|
```
|
|
176
177
|
|
|
178
|
+
Delegating to a running MyCode server:
|
|
179
|
+
```bash
|
|
180
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./path/to/codebase
|
|
181
|
+
```
|
|
182
|
+
|
|
177
183
|
### Step 2 — Generate code
|
|
178
184
|
|
|
179
185
|
```bash
|
|
@@ -191,6 +197,9 @@ my-code --backend claude generate "write a binary search function"
|
|
|
191
197
|
|
|
192
198
|
# Override the model
|
|
193
199
|
my-code --backend claude --model claude-sonnet-4-6 generate "write a rate limiter"
|
|
200
|
+
|
|
201
|
+
# Delegate to a running MyCode server
|
|
202
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a rate limiter"
|
|
194
203
|
```
|
|
195
204
|
|
|
196
205
|
---
|
|
@@ -201,11 +210,10 @@ my-code --backend claude --model claude-sonnet-4-6 generate "write a rate limite
|
|
|
201
210
|
my-code [OPTIONS] COMMAND
|
|
202
211
|
|
|
203
212
|
Options:
|
|
204
|
-
--backend {
|
|
213
|
+
--backend {claude,openai,mcp} AI backend to use (default: claude)
|
|
205
214
|
--api-key TEXT API key for claude/openai backends
|
|
206
215
|
--model TEXT Override the default model
|
|
207
|
-
--
|
|
208
|
-
--mcp-url TEXT Custom MCP server URL (default: http://localhost:8001/mcp)
|
|
216
|
+
--mcp-url TEXT MCP server URL (default: http://localhost:8001/mcp)
|
|
209
217
|
--profile TEXT Path to style profile JSON (default: style_profile.json)
|
|
210
218
|
|
|
211
219
|
Commands:
|
|
@@ -335,6 +343,24 @@ my-code stop --pid-file mycode-8080.pid
|
|
|
335
343
|
my-code stop --pid-file mycode-8081.pid
|
|
336
344
|
```
|
|
337
345
|
|
|
346
|
+
### Using the server from the CLI
|
|
347
|
+
|
|
348
|
+
Start a MyCode server as a daemon, then point `analyze` and `generate` at it with `--backend mcp`. The CLI calls the server's tools directly — the server handles all analysis and generation using whichever LLM it was started with.
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
# Start the server (uses a local LLM at port 8001 as its brain)
|
|
352
|
+
my-code --backend mcp --mcp-url http://localhost:8001/mcp serve --daemon --port 8000
|
|
353
|
+
|
|
354
|
+
# Analyze a codebase via the server
|
|
355
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./my_project
|
|
356
|
+
|
|
357
|
+
# Generate code via the server (loads style_profile.json locally and sends it)
|
|
358
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a retry decorator"
|
|
359
|
+
|
|
360
|
+
# Stop the server
|
|
361
|
+
my-code stop
|
|
362
|
+
```
|
|
363
|
+
|
|
338
364
|
### Connecting from an MCP consumer
|
|
339
365
|
|
|
340
366
|
Add the printed snippet to your consumer's MCP config file (e.g. `.mcp.json`):
|
|
@@ -414,12 +440,11 @@ Both test suites use a `MockBackend` — no live AI backend required.
|
|
|
414
440
|
my_code/
|
|
415
441
|
├── analyzer.py # StyleAnalyzer — scans files, builds style profile
|
|
416
442
|
├── generator.py # generate_code() — formats prompt and calls backend
|
|
417
|
-
├── mcp_client.py #
|
|
443
|
+
├── mcp_client.py # MCPClient (raw LLM wrapper) + MyCodeClient (server delegation)
|
|
418
444
|
├── server.py # MCPServer — exposes analyze/generate as MCP tools
|
|
419
445
|
├── cli.py # CLI entry point (my-code command)
|
|
420
446
|
├── backends/
|
|
421
447
|
│ ├── base.py # AIBackend abstract base class
|
|
422
|
-
│ ├── ricky_backend.py # Local LLM backend (connects via MCP)
|
|
423
448
|
│ ├── claude_backend.py
|
|
424
449
|
│ ├── openai_backend.py
|
|
425
450
|
│ └── mcp_backend.py # Generic MCP server backend
|
|
@@ -2,7 +2,7 @@ __version__ = "0.3.0"
|
|
|
2
2
|
|
|
3
3
|
from .analyzer import StyleAnalyzer
|
|
4
4
|
from .generator import generate_code
|
|
5
|
-
from .backends import AIBackend, ClaudeBackend, OpenAIBackend,
|
|
5
|
+
from .backends import AIBackend, ClaudeBackend, OpenAIBackend, MCPBackend, make_backend
|
|
6
6
|
from .server import MCPServer
|
|
7
7
|
|
|
8
8
|
|
|
@@ -17,7 +17,6 @@ __all__ = [
|
|
|
17
17
|
"AIBackend",
|
|
18
18
|
"ClaudeBackend",
|
|
19
19
|
"OpenAIBackend",
|
|
20
|
-
"RickyBackend",
|
|
21
20
|
"MCPBackend",
|
|
22
21
|
"make_backend",
|
|
23
22
|
"MCPServer",
|
|
@@ -3,16 +3,14 @@ import os
|
|
|
3
3
|
from .base import AIBackend
|
|
4
4
|
from .claude_backend import ClaudeBackend
|
|
5
5
|
from .openai_backend import OpenAIBackend
|
|
6
|
-
from .ricky_backend import RickyBackend
|
|
7
6
|
from .mcp_backend import MCPBackend
|
|
8
7
|
|
|
9
|
-
__all__ = ["AIBackend", "ClaudeBackend", "OpenAIBackend", "
|
|
8
|
+
__all__ = ["AIBackend", "ClaudeBackend", "OpenAIBackend", "MCPBackend", "make_backend"]
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
def make_backend(
|
|
13
|
-
backend: str = "
|
|
12
|
+
backend: str = "claude",
|
|
14
13
|
api_key: str | None = None,
|
|
15
|
-
ricky_url: str = "http://localhost:8000/mcp",
|
|
16
14
|
mcp_url: str = "http://localhost:8001/mcp",
|
|
17
15
|
model: str | None = None,
|
|
18
16
|
timeout: int = 120,
|
|
@@ -29,6 +27,4 @@ def make_backend(
|
|
|
29
27
|
return OpenAIBackend(api_key=key, **{"model": model} if model else {})
|
|
30
28
|
if backend == "mcp":
|
|
31
29
|
return MCPBackend(url=mcp_url, timeout=timeout)
|
|
32
|
-
|
|
33
|
-
return RickyBackend(url=ricky_url, timeout=timeout)
|
|
34
|
-
raise ValueError(f"Unknown backend {backend!r}. Choose: llama, claude, openai, mcp")
|
|
30
|
+
raise ValueError(f"Unknown backend {backend!r}. Choose: claude, openai, mcp")
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Style-aware code generation agent.
|
|
3
3
|
|
|
4
4
|
Usage:
|
|
5
|
-
my-code [--backend
|
|
6
|
-
my-code [--backend
|
|
5
|
+
my-code [--backend claude|openai|mcp] analyze <dir>
|
|
6
|
+
my-code [--backend claude|openai|mcp] --api-key <key> generate "<task>"
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import argparse
|
|
@@ -17,6 +17,7 @@ from pathlib import Path
|
|
|
17
17
|
from .analyzer import StyleAnalyzer
|
|
18
18
|
from .backends import make_backend
|
|
19
19
|
from .generator import generate_code
|
|
20
|
+
from .mcp_client import MyCodeClient
|
|
20
21
|
from .server import MCPServer
|
|
21
22
|
|
|
22
23
|
DEFAULT_PROFILE = Path("style_profile.json")
|
|
@@ -27,10 +28,16 @@ def cmd_analyze(args):
|
|
|
27
28
|
if not root.exists():
|
|
28
29
|
sys.exit(f"Error: directory not found: {root}")
|
|
29
30
|
|
|
31
|
+
if args.backend == "mcp":
|
|
32
|
+
client = MyCodeClient(args.mcp_url, args.timeout)
|
|
33
|
+
print(f"Analyzing codebase at {root} via MyCode server ...")
|
|
34
|
+
profile = client.analyze_codebase(str(root))
|
|
35
|
+
StyleAnalyzer.save_profile(profile, Path(args.profile))
|
|
36
|
+
return
|
|
37
|
+
|
|
30
38
|
backend = make_backend(
|
|
31
39
|
backend=args.backend,
|
|
32
40
|
api_key=args.api_key,
|
|
33
|
-
ricky_url=args.ricky_url,
|
|
34
41
|
mcp_url=args.mcp_url,
|
|
35
42
|
model=args.model,
|
|
36
43
|
timeout=args.timeout,
|
|
@@ -52,10 +59,16 @@ def cmd_generate(args):
|
|
|
52
59
|
"Run 'analyze' first."
|
|
53
60
|
)
|
|
54
61
|
|
|
62
|
+
if args.backend == "mcp":
|
|
63
|
+
profile = json.loads(profile_path.read_text(encoding="utf-8"))
|
|
64
|
+
client = MyCodeClient(args.mcp_url, args.timeout)
|
|
65
|
+
print("Generating code ...\n")
|
|
66
|
+
print(client.generate_code(args.task, profile=profile))
|
|
67
|
+
return
|
|
68
|
+
|
|
55
69
|
backend = make_backend(
|
|
56
70
|
backend=args.backend,
|
|
57
71
|
api_key=args.api_key,
|
|
58
|
-
ricky_url=args.ricky_url,
|
|
59
72
|
mcp_url=args.mcp_url,
|
|
60
73
|
model=args.model,
|
|
61
74
|
timeout=args.timeout,
|
|
@@ -72,7 +85,6 @@ def _spawn_daemon(args):
|
|
|
72
85
|
cmd = [
|
|
73
86
|
sys.executable, "-m", "my_code",
|
|
74
87
|
"--backend", args.backend,
|
|
75
|
-
"--ricky-url", args.ricky_url,
|
|
76
88
|
"--mcp-url", args.mcp_url,
|
|
77
89
|
"--timeout", str(args.timeout),
|
|
78
90
|
"--profile", args.profile,
|
|
@@ -112,7 +124,6 @@ def cmd_serve(args):
|
|
|
112
124
|
backend = make_backend(
|
|
113
125
|
backend=args.backend,
|
|
114
126
|
api_key=args.api_key,
|
|
115
|
-
ricky_url=args.ricky_url,
|
|
116
127
|
mcp_url=args.mcp_url,
|
|
117
128
|
model=args.model,
|
|
118
129
|
timeout=args.timeout,
|
|
@@ -123,12 +134,11 @@ def cmd_serve(args):
|
|
|
123
134
|
def main():
|
|
124
135
|
parser = argparse.ArgumentParser(description="Style-aware code agent")
|
|
125
136
|
parser.add_argument(
|
|
126
|
-
"--backend", default="
|
|
127
|
-
help="AI backend to use (default:
|
|
137
|
+
"--backend", default="claude", choices=["claude", "openai", "mcp"],
|
|
138
|
+
help="AI backend to use (default: claude)",
|
|
128
139
|
)
|
|
129
140
|
parser.add_argument("--api-key", default=None, help="API key (claude/openai); falls back to env var")
|
|
130
141
|
parser.add_argument("--model", default=None, help="Override default model for claude/openai backends")
|
|
131
|
-
parser.add_argument("--ricky-url", default="http://localhost:8000/mcp", help="Ricky MCP server URL (llama backend)")
|
|
132
142
|
parser.add_argument("--mcp-url", default="http://localhost:8001/mcp", help="MCP server URL (mcp backend)")
|
|
133
143
|
parser.add_argument("--timeout", type=int, default=120, help="Request timeout in seconds (default: 120)")
|
|
134
144
|
parser.add_argument("--profile", default=str(DEFAULT_PROFILE))
|
|
@@ -137,3 +137,29 @@ class MCPClient:
|
|
|
137
137
|
"arguments": {self._analyze_arg: prompt},
|
|
138
138
|
})
|
|
139
139
|
return self._extract_text(result)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class MyCodeClient(MCPClient):
|
|
143
|
+
"""Client for a running MyCode MCP server. Calls semantic tools directly."""
|
|
144
|
+
|
|
145
|
+
def __init__(self, url: str = "http://localhost:8000/mcp", timeout: int = 120):
|
|
146
|
+
self.url = url
|
|
147
|
+
self._timeout = timeout
|
|
148
|
+
self._session_id = None
|
|
149
|
+
self._initialize()
|
|
150
|
+
|
|
151
|
+
def analyze_codebase(self, path: str, save_to: str | None = None) -> dict:
|
|
152
|
+
args = {"path": path}
|
|
153
|
+
if save_to:
|
|
154
|
+
args["save_to"] = save_to
|
|
155
|
+
result = self._post("tools/call", {"name": "analyze_codebase", "arguments": args})
|
|
156
|
+
return json.loads(self._extract_text(result))
|
|
157
|
+
|
|
158
|
+
def generate_code(self, task: str, profile: dict | None = None, profile_path: str | None = None) -> str:
|
|
159
|
+
args = {"task": task}
|
|
160
|
+
if profile is not None:
|
|
161
|
+
args["profile"] = profile
|
|
162
|
+
elif profile_path:
|
|
163
|
+
args["profile_path"] = profile_path
|
|
164
|
+
result = self._post("tools/call", {"name": "generate_code", "arguments": args})
|
|
165
|
+
return self._extract_text(result)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mycode-aiagent
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Style-aware code generation — analyze any codebase and generate new code that matches its style
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/RyanAbbottData/MyCode
|
|
@@ -90,14 +90,15 @@ MyCode delegates inference to a pluggable backend. Choose one based on what you
|
|
|
90
90
|
|
|
91
91
|
| Backend | Flag | Requirement |
|
|
92
92
|
|---|---|---|
|
|
93
|
-
|
|
|
94
|
-
| Anthropic Claude | `--backend claude` | `ANTHROPIC_API_KEY` env var or `--api-key` |
|
|
93
|
+
| Anthropic Claude | `--backend claude` (default) | `ANTHROPIC_API_KEY` env var or `--api-key` |
|
|
95
94
|
| OpenAI | `--backend openai` | `OPENAI_API_KEY` env var or `--api-key` |
|
|
96
|
-
|
|
|
95
|
+
| Any MCP server | `--backend mcp` | An MCP server at `--mcp-url` (MyCode server or local LLM wrapper) |
|
|
96
|
+
|
|
97
|
+
When `--backend mcp` is used with `analyze` or `generate`, the CLI delegates the entire operation to the running MyCode server — it calls the server's `analyze_codebase` or `generate_code` tool directly instead of running the pipeline locally. This is the recommended way to use MyCode in a multi-project or agentic setup.
|
|
97
98
|
|
|
98
99
|
### Setting up a local LLM
|
|
99
100
|
|
|
100
|
-
The `
|
|
101
|
+
The `mcp` backend can connect to any MCP server, including a local LLM wrapper. The server must expose two tools: one for code generation and one for analysis (tool name must contain `"analyze"`). Here is a recommended setup using [llama.cpp](https://github.com/ggerganov/llama.cpp):
|
|
101
102
|
|
|
102
103
|
**1. Download a model**
|
|
103
104
|
|
|
@@ -124,7 +125,7 @@ python -m llama_cpp.server \
|
|
|
124
125
|
|
|
125
126
|
**3. Wrap it with an MCP server**
|
|
126
127
|
|
|
127
|
-
The `
|
|
128
|
+
The `mcp` backend communicates over MCP, not directly with the llama.cpp HTTP API. You need a thin MCP wrapper that exposes two tools:
|
|
128
129
|
- A **code generation tool** (name must not contain `"analyze"`)
|
|
129
130
|
- An **analysis tool** (name must contain `"analyze"`)
|
|
130
131
|
|
|
@@ -166,13 +167,13 @@ python llm_mcp_server.py
|
|
|
166
167
|
**4. Point MyCode at it**
|
|
167
168
|
|
|
168
169
|
```bash
|
|
169
|
-
my-code --backend
|
|
170
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze .
|
|
170
171
|
```
|
|
171
172
|
|
|
172
173
|
Or set a custom URL if your server runs on a different port:
|
|
173
174
|
|
|
174
175
|
```bash
|
|
175
|
-
my-code --backend
|
|
176
|
+
my-code --backend mcp --mcp-url http://localhost:9000/mcp analyze .
|
|
176
177
|
```
|
|
177
178
|
|
|
178
179
|
---
|
|
@@ -203,6 +204,11 @@ The profile is saved to `style_profile.json` by default. Specify a different pat
|
|
|
203
204
|
my-code analyze ./path/to/codebase --profile ./profiles/my_team.json
|
|
204
205
|
```
|
|
205
206
|
|
|
207
|
+
Delegating to a running MyCode server:
|
|
208
|
+
```bash
|
|
209
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./path/to/codebase
|
|
210
|
+
```
|
|
211
|
+
|
|
206
212
|
### Step 2 — Generate code
|
|
207
213
|
|
|
208
214
|
```bash
|
|
@@ -220,6 +226,9 @@ my-code --backend claude generate "write a binary search function"
|
|
|
220
226
|
|
|
221
227
|
# Override the model
|
|
222
228
|
my-code --backend claude --model claude-sonnet-4-6 generate "write a rate limiter"
|
|
229
|
+
|
|
230
|
+
# Delegate to a running MyCode server
|
|
231
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a rate limiter"
|
|
223
232
|
```
|
|
224
233
|
|
|
225
234
|
---
|
|
@@ -230,11 +239,10 @@ my-code --backend claude --model claude-sonnet-4-6 generate "write a rate limite
|
|
|
230
239
|
my-code [OPTIONS] COMMAND
|
|
231
240
|
|
|
232
241
|
Options:
|
|
233
|
-
--backend {
|
|
242
|
+
--backend {claude,openai,mcp} AI backend to use (default: claude)
|
|
234
243
|
--api-key TEXT API key for claude/openai backends
|
|
235
244
|
--model TEXT Override the default model
|
|
236
|
-
--
|
|
237
|
-
--mcp-url TEXT Custom MCP server URL (default: http://localhost:8001/mcp)
|
|
245
|
+
--mcp-url TEXT MCP server URL (default: http://localhost:8001/mcp)
|
|
238
246
|
--profile TEXT Path to style profile JSON (default: style_profile.json)
|
|
239
247
|
|
|
240
248
|
Commands:
|
|
@@ -364,6 +372,24 @@ my-code stop --pid-file mycode-8080.pid
|
|
|
364
372
|
my-code stop --pid-file mycode-8081.pid
|
|
365
373
|
```
|
|
366
374
|
|
|
375
|
+
### Using the server from the CLI
|
|
376
|
+
|
|
377
|
+
Start a MyCode server as a daemon, then point `analyze` and `generate` at it with `--backend mcp`. The CLI calls the server's tools directly — the server handles all analysis and generation using whichever LLM it was started with.
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
# Start the server (uses a local LLM at port 8001 as its brain)
|
|
381
|
+
my-code --backend mcp --mcp-url http://localhost:8001/mcp serve --daemon --port 8000
|
|
382
|
+
|
|
383
|
+
# Analyze a codebase via the server
|
|
384
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./my_project
|
|
385
|
+
|
|
386
|
+
# Generate code via the server (loads style_profile.json locally and sends it)
|
|
387
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a retry decorator"
|
|
388
|
+
|
|
389
|
+
# Stop the server
|
|
390
|
+
my-code stop
|
|
391
|
+
```
|
|
392
|
+
|
|
367
393
|
### Connecting from an MCP consumer
|
|
368
394
|
|
|
369
395
|
Add the printed snippet to your consumer's MCP config file (e.g. `.mcp.json`):
|
|
@@ -443,12 +469,11 @@ Both test suites use a `MockBackend` — no live AI backend required.
|
|
|
443
469
|
my_code/
|
|
444
470
|
├── analyzer.py # StyleAnalyzer — scans files, builds style profile
|
|
445
471
|
├── generator.py # generate_code() — formats prompt and calls backend
|
|
446
|
-
├── mcp_client.py #
|
|
472
|
+
├── mcp_client.py # MCPClient (raw LLM wrapper) + MyCodeClient (server delegation)
|
|
447
473
|
├── server.py # MCPServer — exposes analyze/generate as MCP tools
|
|
448
474
|
├── cli.py # CLI entry point (my-code command)
|
|
449
475
|
├── backends/
|
|
450
476
|
│ ├── base.py # AIBackend abstract base class
|
|
451
|
-
│ ├── ricky_backend.py # Local LLM backend (connects via MCP)
|
|
452
477
|
│ ├── claude_backend.py
|
|
453
478
|
│ ├── openai_backend.py
|
|
454
479
|
│ └── mcp_backend.py # Generic MCP server backend
|
|
@@ -12,7 +12,6 @@ my_code/backends/base.py
|
|
|
12
12
|
my_code/backends/claude_backend.py
|
|
13
13
|
my_code/backends/mcp_backend.py
|
|
14
14
|
my_code/backends/openai_backend.py
|
|
15
|
-
my_code/backends/ricky_backend.py
|
|
16
15
|
my_code/utils/__init__.py
|
|
17
16
|
my_code/utils/prompts.py
|
|
18
17
|
mycode_aiagent.egg-info/PKG-INFO
|
|
@@ -21,4 +20,5 @@ mycode_aiagent.egg-info/dependency_links.txt
|
|
|
21
20
|
mycode_aiagent.egg-info/entry_points.txt
|
|
22
21
|
mycode_aiagent.egg-info/requires.txt
|
|
23
22
|
mycode_aiagent.egg-info/top_level.txt
|
|
24
|
-
tests/test_library.py
|
|
23
|
+
tests/test_library.py
|
|
24
|
+
tests/test_server.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mycode-aiagent"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.2"
|
|
8
8
|
description = "Style-aware code generation — analyze any codebase and generate new code that matches its style"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for the MyCode MCP server.
|
|
3
|
+
Spins up a real ThreadingHTTPServer against a MockBackend and exercises the protocol.
|
|
4
|
+
No live AI backend required.
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
import socket
|
|
8
|
+
import threading
|
|
9
|
+
import unittest
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import requests
|
|
13
|
+
|
|
14
|
+
from my_code.server import MCPServer
|
|
15
|
+
from my_code import AIBackend
|
|
16
|
+
|
|
17
|
+
# ── Mock backend (same contract as the one in test_library.py) ────────────────
|
|
18
|
+
|
|
19
|
+
MOCK_PROFILE = {
|
|
20
|
+
"naming": {"functions": "snake_case", "classes": "PascalCase"},
|
|
21
|
+
"comments": {"docstring_style": "plain"},
|
|
22
|
+
"representative_snippets": ["def foo(x: int) -> str:\n return str(x)"],
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class MockBackend(AIBackend):
|
|
26
|
+
max_file_chars = 6000
|
|
27
|
+
|
|
28
|
+
def ask_to_analyze(self, prompt: str) -> str:
|
|
29
|
+
return json.dumps(MOCK_PROFILE)
|
|
30
|
+
|
|
31
|
+
def ask_for_code(self, prompt: str) -> str:
|
|
32
|
+
return "def hello(name: str) -> str:\n return f'Hello, {name}'"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _free_port() -> int:
|
|
36
|
+
with socket.socket() as s:
|
|
37
|
+
s.bind(("127.0.0.1", 0))
|
|
38
|
+
return s.getsockname()[1]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _post(url: str, method: str, params: dict, session_id: str | None = None) -> requests.Response:
|
|
42
|
+
headers = {
|
|
43
|
+
"Content-Type": "application/json",
|
|
44
|
+
"Accept": "application/json, text/event-stream",
|
|
45
|
+
}
|
|
46
|
+
if session_id:
|
|
47
|
+
headers["Mcp-Session-Id"] = session_id
|
|
48
|
+
return requests.post(
|
|
49
|
+
url,
|
|
50
|
+
json={"jsonrpc": "2.0", "id": "1", "method": method, "params": params},
|
|
51
|
+
headers=headers,
|
|
52
|
+
timeout=10,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _parse(resp: requests.Response) -> dict:
|
|
57
|
+
for line in resp.text.splitlines():
|
|
58
|
+
if line.startswith("data:"):
|
|
59
|
+
return json.loads(line[5:].strip())
|
|
60
|
+
raise AssertionError(f"No SSE data line in response: {resp.text[:300]}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class TestMCPServer(unittest.TestCase):
|
|
64
|
+
@classmethod
|
|
65
|
+
def setUpClass(cls):
|
|
66
|
+
port = _free_port()
|
|
67
|
+
cls.url = f"http://127.0.0.1:{port}/mcp"
|
|
68
|
+
server = MCPServer(MockBackend(), host="127.0.0.1", port=port)
|
|
69
|
+
cls.httpd = server.start()
|
|
70
|
+
t = threading.Thread(target=cls.httpd.serve_forever, daemon=True)
|
|
71
|
+
t.start()
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def tearDownClass(cls):
|
|
75
|
+
cls.httpd.shutdown()
|
|
76
|
+
|
|
77
|
+
def test_initialize(self):
|
|
78
|
+
resp = _post(self.url, "initialize", {
|
|
79
|
+
"protocolVersion": "2024-11-05",
|
|
80
|
+
"capabilities": {},
|
|
81
|
+
"clientInfo": {"name": "test", "version": "1"},
|
|
82
|
+
})
|
|
83
|
+
self.assertEqual(resp.status_code, 200)
|
|
84
|
+
self.assertIn("Mcp-Session-Id", resp.headers)
|
|
85
|
+
msg = _parse(resp)
|
|
86
|
+
self.assertIn("result", msg)
|
|
87
|
+
self.assertEqual(msg["result"]["serverInfo"]["name"], "mycode")
|
|
88
|
+
|
|
89
|
+
def test_tools_list(self):
|
|
90
|
+
resp = _post(self.url, "tools/list", {})
|
|
91
|
+
self.assertEqual(resp.status_code, 200)
|
|
92
|
+
msg = _parse(resp)
|
|
93
|
+
tools = {t["name"] for t in msg["result"]["tools"]}
|
|
94
|
+
self.assertEqual(tools, {"analyze_codebase", "generate_code"})
|
|
95
|
+
|
|
96
|
+
def test_analyze_codebase(self):
|
|
97
|
+
path = str(Path(__file__).parent.parent / "my_code")
|
|
98
|
+
resp = _post(self.url, "tools/call", {
|
|
99
|
+
"name": "analyze_codebase",
|
|
100
|
+
"arguments": {"path": path},
|
|
101
|
+
})
|
|
102
|
+
self.assertEqual(resp.status_code, 200)
|
|
103
|
+
msg = _parse(resp)
|
|
104
|
+
text = msg["result"]["content"][0]["text"]
|
|
105
|
+
profile = json.loads(text)
|
|
106
|
+
self.assertIn("naming", profile)
|
|
107
|
+
|
|
108
|
+
def test_generate_code(self):
|
|
109
|
+
resp = _post(self.url, "tools/call", {
|
|
110
|
+
"name": "generate_code",
|
|
111
|
+
"arguments": {"task": "write a greeting function", "profile": MOCK_PROFILE},
|
|
112
|
+
})
|
|
113
|
+
self.assertEqual(resp.status_code, 200)
|
|
114
|
+
msg = _parse(resp)
|
|
115
|
+
code = msg["result"]["content"][0]["text"]
|
|
116
|
+
self.assertTrue(len(code) > 0)
|
|
117
|
+
self.assertIn("def", code)
|
|
118
|
+
|
|
119
|
+
def test_unknown_method(self):
|
|
120
|
+
resp = _post(self.url, "nonexistent/method", {})
|
|
121
|
+
self.assertEqual(resp.status_code, 200)
|
|
122
|
+
msg = _parse(resp)
|
|
123
|
+
self.assertIn("error", msg)
|
|
124
|
+
self.assertEqual(msg["error"]["code"], -32601)
|
|
125
|
+
|
|
126
|
+
def test_unknown_tool(self):
|
|
127
|
+
resp = _post(self.url, "tools/call", {
|
|
128
|
+
"name": "does_not_exist",
|
|
129
|
+
"arguments": {},
|
|
130
|
+
})
|
|
131
|
+
self.assertEqual(resp.status_code, 200)
|
|
132
|
+
msg = _parse(resp)
|
|
133
|
+
self.assertIn("error", msg)
|
|
134
|
+
self.assertEqual(msg["error"]["code"], -32602)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
unittest.main()
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
from .base import AIBackend
|
|
2
|
-
from ..mcp_client import MCPClient
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class RickyBackend(AIBackend):
|
|
6
|
-
max_file_chars: int = 1500
|
|
7
|
-
|
|
8
|
-
def __init__(self, url: str = "http://localhost:8000/mcp", timeout: int = 120):
|
|
9
|
-
self._client = MCPClient(url=url, timeout=timeout)
|
|
10
|
-
|
|
11
|
-
def ask_for_code(self, prompt: str) -> str:
|
|
12
|
-
return self._client.ask_for_code(prompt)
|
|
13
|
-
|
|
14
|
-
def ask_to_analyze(self, prompt: str) -> str:
|
|
15
|
-
return self._client.ask_to_analyze(prompt)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|