mycode-aiagent 0.3.1__tar.gz → 0.4.1__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.3.1 → mycode_aiagent-0.4.1}/PKG-INFO +66 -7
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/README.md +65 -6
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/cli.py +64 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/mcp_client.py +26 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/PKG-INFO +66 -7
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/SOURCES.txt +2 -1
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/pyproject.toml +1 -1
- mycode_aiagent-0.4.1/tests/test_server.py +138 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/__init__.py +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/__main__.py +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/analyzer.py +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/backends/__init__.py +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/backends/base.py +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/backends/claude_backend.py +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/backends/mcp_backend.py +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/backends/openai_backend.py +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/backends/ricky_backend.py +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/generator.py +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/server.py +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/utils/__init__.py +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/utils/prompts.py +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/dependency_links.txt +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/entry_points.txt +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/requires.txt +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/top_level.txt +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/setup.cfg +0 -0
- {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/tests/test_library.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mycode-aiagent
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
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
|
|
@@ -93,7 +93,9 @@ MyCode delegates inference to a pluggable backend. Choose one based on what you
|
|
|
93
93
|
| Local LLM | `--backend llama` (default) | MCP server running at `localhost:8000` |
|
|
94
94
|
| Anthropic Claude | `--backend claude` | `ANTHROPIC_API_KEY` env var or `--api-key` |
|
|
95
95
|
| OpenAI | `--backend openai` | `OPENAI_API_KEY` env var or `--api-key` |
|
|
96
|
-
|
|
|
96
|
+
| MyCode server | `--backend mcp` | A running MyCode MCP server at `--mcp-url` |
|
|
97
|
+
|
|
98
|
+
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
99
|
|
|
98
100
|
### Setting up a local LLM
|
|
99
101
|
|
|
@@ -203,6 +205,11 @@ The profile is saved to `style_profile.json` by default. Specify a different pat
|
|
|
203
205
|
my-code analyze ./path/to/codebase --profile ./profiles/my_team.json
|
|
204
206
|
```
|
|
205
207
|
|
|
208
|
+
Delegating to a running MyCode server:
|
|
209
|
+
```bash
|
|
210
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./path/to/codebase
|
|
211
|
+
```
|
|
212
|
+
|
|
206
213
|
### Step 2 — Generate code
|
|
207
214
|
|
|
208
215
|
```bash
|
|
@@ -220,6 +227,9 @@ my-code --backend claude generate "write a binary search function"
|
|
|
220
227
|
|
|
221
228
|
# Override the model
|
|
222
229
|
my-code --backend claude --model claude-sonnet-4-6 generate "write a rate limiter"
|
|
230
|
+
|
|
231
|
+
# Delegate to a running MyCode server
|
|
232
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a rate limiter"
|
|
223
233
|
```
|
|
224
234
|
|
|
225
235
|
---
|
|
@@ -243,9 +253,14 @@ Commands:
|
|
|
243
253
|
|
|
244
254
|
generate TASK Generate code matching the saved style profile
|
|
245
255
|
|
|
246
|
-
serve
|
|
247
|
-
--host TEXT
|
|
248
|
-
--port INT
|
|
256
|
+
serve Start an MCP server (blocks until Ctrl-C)
|
|
257
|
+
--host TEXT Host to bind (default: 127.0.0.1)
|
|
258
|
+
--port INT Port to listen on (default: 8080)
|
|
259
|
+
--daemon Run as a detached background process
|
|
260
|
+
--pid-file TEXT PID file path for daemon mode (default: mycode.pid)
|
|
261
|
+
|
|
262
|
+
stop Stop a running daemon server
|
|
263
|
+
--pid-file TEXT PID file written by 'serve --daemon' (default: mycode.pid)
|
|
249
264
|
```
|
|
250
265
|
|
|
251
266
|
---
|
|
@@ -313,7 +328,7 @@ MyCode can expose itself as an MCP server so any MCP-compatible agent or orchest
|
|
|
313
328
|
### Quick start
|
|
314
329
|
|
|
315
330
|
```bash
|
|
316
|
-
# Local LLM backend (default)
|
|
331
|
+
# Local LLM backend (default) — foreground, blocks until Ctrl-C
|
|
317
332
|
my-code serve
|
|
318
333
|
|
|
319
334
|
# Claude backend
|
|
@@ -333,6 +348,50 @@ MyCode MCP server running at http://127.0.0.1:8080/mcp
|
|
|
333
348
|
Add to your MCP config: {"mycode": {"url": "http://127.0.0.1:8080/mcp"}}
|
|
334
349
|
```
|
|
335
350
|
|
|
351
|
+
### Running as a daemon
|
|
352
|
+
|
|
353
|
+
Add `--daemon` to run the server as a detached background process. The terminal returns immediately and the server keeps running.
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
my-code --backend claude serve --daemon
|
|
357
|
+
# → MyCode MCP server started as daemon (PID 12345) at http://127.0.0.1:8080/mcp
|
|
358
|
+
# → Stop with: my-code stop
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
The PID is written to `mycode.pid` by default. Stop the server with:
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
my-code stop
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
When running multiple instances on different ports, use `--pid-file` to keep them separate:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
my-code --backend claude serve --port 8080 --daemon --pid-file mycode-8080.pid
|
|
371
|
+
my-code --backend openai serve --port 8081 --daemon --pid-file mycode-8081.pid
|
|
372
|
+
|
|
373
|
+
my-code stop --pid-file mycode-8080.pid
|
|
374
|
+
my-code stop --pid-file mycode-8081.pid
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Using the server from the CLI
|
|
378
|
+
|
|
379
|
+
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.
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
# Start the server (uses a local LLM at port 8001 as its brain)
|
|
383
|
+
my-code --backend mcp --mcp-url http://localhost:8001/mcp serve --daemon --port 8000
|
|
384
|
+
|
|
385
|
+
# Analyze a codebase via the server
|
|
386
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./my_project
|
|
387
|
+
|
|
388
|
+
# Generate code via the server (loads style_profile.json locally and sends it)
|
|
389
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a retry decorator"
|
|
390
|
+
|
|
391
|
+
# Stop the server
|
|
392
|
+
my-code stop
|
|
393
|
+
```
|
|
394
|
+
|
|
336
395
|
### Connecting from an MCP consumer
|
|
337
396
|
|
|
338
397
|
Add the printed snippet to your consumer's MCP config file (e.g. `.mcp.json`):
|
|
@@ -412,7 +471,7 @@ Both test suites use a `MockBackend` — no live AI backend required.
|
|
|
412
471
|
my_code/
|
|
413
472
|
├── analyzer.py # StyleAnalyzer — scans files, builds style profile
|
|
414
473
|
├── generator.py # generate_code() — formats prompt and calls backend
|
|
415
|
-
├── mcp_client.py #
|
|
474
|
+
├── mcp_client.py # MCPClient (raw LLM wrapper) + MyCodeClient (server delegation)
|
|
416
475
|
├── server.py # MCPServer — exposes analyze/generate as MCP tools
|
|
417
476
|
├── cli.py # CLI entry point (my-code command)
|
|
418
477
|
├── backends/
|
|
@@ -64,7 +64,9 @@ MyCode delegates inference to a pluggable backend. Choose one based on what you
|
|
|
64
64
|
| Local LLM | `--backend llama` (default) | MCP server running at `localhost:8000` |
|
|
65
65
|
| Anthropic Claude | `--backend claude` | `ANTHROPIC_API_KEY` env var or `--api-key` |
|
|
66
66
|
| OpenAI | `--backend openai` | `OPENAI_API_KEY` env var or `--api-key` |
|
|
67
|
-
|
|
|
67
|
+
| MyCode server | `--backend mcp` | A running MyCode MCP server at `--mcp-url` |
|
|
68
|
+
|
|
69
|
+
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
70
|
|
|
69
71
|
### Setting up a local LLM
|
|
70
72
|
|
|
@@ -174,6 +176,11 @@ The profile is saved to `style_profile.json` by default. Specify a different pat
|
|
|
174
176
|
my-code analyze ./path/to/codebase --profile ./profiles/my_team.json
|
|
175
177
|
```
|
|
176
178
|
|
|
179
|
+
Delegating to a running MyCode server:
|
|
180
|
+
```bash
|
|
181
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./path/to/codebase
|
|
182
|
+
```
|
|
183
|
+
|
|
177
184
|
### Step 2 — Generate code
|
|
178
185
|
|
|
179
186
|
```bash
|
|
@@ -191,6 +198,9 @@ my-code --backend claude generate "write a binary search function"
|
|
|
191
198
|
|
|
192
199
|
# Override the model
|
|
193
200
|
my-code --backend claude --model claude-sonnet-4-6 generate "write a rate limiter"
|
|
201
|
+
|
|
202
|
+
# Delegate to a running MyCode server
|
|
203
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a rate limiter"
|
|
194
204
|
```
|
|
195
205
|
|
|
196
206
|
---
|
|
@@ -214,9 +224,14 @@ Commands:
|
|
|
214
224
|
|
|
215
225
|
generate TASK Generate code matching the saved style profile
|
|
216
226
|
|
|
217
|
-
serve
|
|
218
|
-
--host TEXT
|
|
219
|
-
--port INT
|
|
227
|
+
serve Start an MCP server (blocks until Ctrl-C)
|
|
228
|
+
--host TEXT Host to bind (default: 127.0.0.1)
|
|
229
|
+
--port INT Port to listen on (default: 8080)
|
|
230
|
+
--daemon Run as a detached background process
|
|
231
|
+
--pid-file TEXT PID file path for daemon mode (default: mycode.pid)
|
|
232
|
+
|
|
233
|
+
stop Stop a running daemon server
|
|
234
|
+
--pid-file TEXT PID file written by 'serve --daemon' (default: mycode.pid)
|
|
220
235
|
```
|
|
221
236
|
|
|
222
237
|
---
|
|
@@ -284,7 +299,7 @@ MyCode can expose itself as an MCP server so any MCP-compatible agent or orchest
|
|
|
284
299
|
### Quick start
|
|
285
300
|
|
|
286
301
|
```bash
|
|
287
|
-
# Local LLM backend (default)
|
|
302
|
+
# Local LLM backend (default) — foreground, blocks until Ctrl-C
|
|
288
303
|
my-code serve
|
|
289
304
|
|
|
290
305
|
# Claude backend
|
|
@@ -304,6 +319,50 @@ MyCode MCP server running at http://127.0.0.1:8080/mcp
|
|
|
304
319
|
Add to your MCP config: {"mycode": {"url": "http://127.0.0.1:8080/mcp"}}
|
|
305
320
|
```
|
|
306
321
|
|
|
322
|
+
### Running as a daemon
|
|
323
|
+
|
|
324
|
+
Add `--daemon` to run the server as a detached background process. The terminal returns immediately and the server keeps running.
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
my-code --backend claude serve --daemon
|
|
328
|
+
# → MyCode MCP server started as daemon (PID 12345) at http://127.0.0.1:8080/mcp
|
|
329
|
+
# → Stop with: my-code stop
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
The PID is written to `mycode.pid` by default. Stop the server with:
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
my-code stop
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
When running multiple instances on different ports, use `--pid-file` to keep them separate:
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
my-code --backend claude serve --port 8080 --daemon --pid-file mycode-8080.pid
|
|
342
|
+
my-code --backend openai serve --port 8081 --daemon --pid-file mycode-8081.pid
|
|
343
|
+
|
|
344
|
+
my-code stop --pid-file mycode-8080.pid
|
|
345
|
+
my-code stop --pid-file mycode-8081.pid
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Using the server from the CLI
|
|
349
|
+
|
|
350
|
+
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.
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
# Start the server (uses a local LLM at port 8001 as its brain)
|
|
354
|
+
my-code --backend mcp --mcp-url http://localhost:8001/mcp serve --daemon --port 8000
|
|
355
|
+
|
|
356
|
+
# Analyze a codebase via the server
|
|
357
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./my_project
|
|
358
|
+
|
|
359
|
+
# Generate code via the server (loads style_profile.json locally and sends it)
|
|
360
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a retry decorator"
|
|
361
|
+
|
|
362
|
+
# Stop the server
|
|
363
|
+
my-code stop
|
|
364
|
+
```
|
|
365
|
+
|
|
307
366
|
### Connecting from an MCP consumer
|
|
308
367
|
|
|
309
368
|
Add the printed snippet to your consumer's MCP config file (e.g. `.mcp.json`):
|
|
@@ -383,7 +442,7 @@ Both test suites use a `MockBackend` — no live AI backend required.
|
|
|
383
442
|
my_code/
|
|
384
443
|
├── analyzer.py # StyleAnalyzer — scans files, builds style profile
|
|
385
444
|
├── generator.py # generate_code() — formats prompt and calls backend
|
|
386
|
-
├── mcp_client.py #
|
|
445
|
+
├── mcp_client.py # MCPClient (raw LLM wrapper) + MyCodeClient (server delegation)
|
|
387
446
|
├── server.py # MCPServer — exposes analyze/generate as MCP tools
|
|
388
447
|
├── cli.py # CLI entry point (my-code command)
|
|
389
448
|
├── backends/
|
|
@@ -8,12 +8,16 @@ Usage:
|
|
|
8
8
|
|
|
9
9
|
import argparse
|
|
10
10
|
import json
|
|
11
|
+
import os
|
|
12
|
+
import signal
|
|
13
|
+
import subprocess
|
|
11
14
|
import sys
|
|
12
15
|
from pathlib import Path
|
|
13
16
|
|
|
14
17
|
from .analyzer import StyleAnalyzer
|
|
15
18
|
from .backends import make_backend
|
|
16
19
|
from .generator import generate_code
|
|
20
|
+
from .mcp_client import MyCodeClient
|
|
17
21
|
from .server import MCPServer
|
|
18
22
|
|
|
19
23
|
DEFAULT_PROFILE = Path("style_profile.json")
|
|
@@ -24,6 +28,13 @@ def cmd_analyze(args):
|
|
|
24
28
|
if not root.exists():
|
|
25
29
|
sys.exit(f"Error: directory not found: {root}")
|
|
26
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
|
+
|
|
27
38
|
backend = make_backend(
|
|
28
39
|
backend=args.backend,
|
|
29
40
|
api_key=args.api_key,
|
|
@@ -49,6 +60,13 @@ def cmd_generate(args):
|
|
|
49
60
|
"Run 'analyze' first."
|
|
50
61
|
)
|
|
51
62
|
|
|
63
|
+
if args.backend == "mcp":
|
|
64
|
+
profile = json.loads(profile_path.read_text(encoding="utf-8"))
|
|
65
|
+
client = MyCodeClient(args.mcp_url, args.timeout)
|
|
66
|
+
print("Generating code ...\n")
|
|
67
|
+
print(client.generate_code(args.task, profile=profile))
|
|
68
|
+
return
|
|
69
|
+
|
|
52
70
|
backend = make_backend(
|
|
53
71
|
backend=args.backend,
|
|
54
72
|
api_key=args.api_key,
|
|
@@ -65,7 +83,47 @@ def cmd_generate(args):
|
|
|
65
83
|
print(code)
|
|
66
84
|
|
|
67
85
|
|
|
86
|
+
def _spawn_daemon(args):
|
|
87
|
+
cmd = [
|
|
88
|
+
sys.executable, "-m", "my_code",
|
|
89
|
+
"--backend", args.backend,
|
|
90
|
+
"--ricky-url", args.ricky_url,
|
|
91
|
+
"--mcp-url", args.mcp_url,
|
|
92
|
+
"--timeout", str(args.timeout),
|
|
93
|
+
"--profile", args.profile,
|
|
94
|
+
"serve",
|
|
95
|
+
"--host", args.host,
|
|
96
|
+
"--port", str(args.port),
|
|
97
|
+
]
|
|
98
|
+
if args.api_key:
|
|
99
|
+
cmd += ["--api-key", args.api_key]
|
|
100
|
+
if args.model:
|
|
101
|
+
cmd += ["--model", args.model]
|
|
102
|
+
|
|
103
|
+
kwargs = {"stdout": subprocess.DEVNULL, "stderr": subprocess.DEVNULL}
|
|
104
|
+
if sys.platform == "win32":
|
|
105
|
+
kwargs["creationflags"] = subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
|
|
106
|
+
else:
|
|
107
|
+
kwargs["start_new_session"] = True
|
|
108
|
+
|
|
109
|
+
proc = subprocess.Popen(cmd, **kwargs)
|
|
110
|
+
Path(args.pid_file).write_text(str(proc.pid))
|
|
111
|
+
print(f"MyCode MCP server started as daemon (PID {proc.pid}) at http://{args.host}:{args.port}/mcp")
|
|
112
|
+
print(f"Stop with: my-code stop --pid-file {args.pid_file}")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def cmd_stop(args):
|
|
116
|
+
pid_file = Path(args.pid_file)
|
|
117
|
+
pid = int(pid_file.read_text().strip())
|
|
118
|
+
os.kill(pid, signal.SIGTERM)
|
|
119
|
+
pid_file.unlink()
|
|
120
|
+
print(f"MyCode MCP server (PID {pid}) stopped.")
|
|
121
|
+
|
|
122
|
+
|
|
68
123
|
def cmd_serve(args):
|
|
124
|
+
if args.daemon:
|
|
125
|
+
_spawn_daemon(args)
|
|
126
|
+
return
|
|
69
127
|
backend = make_backend(
|
|
70
128
|
backend=args.backend,
|
|
71
129
|
api_key=args.api_key,
|
|
@@ -104,8 +162,14 @@ def main():
|
|
|
104
162
|
p_serve = sub.add_parser("serve", help="Start an MCP server exposing analyze and generate as tools")
|
|
105
163
|
p_serve.add_argument("--host", default="127.0.0.1", help="Host to bind (default: 127.0.0.1)")
|
|
106
164
|
p_serve.add_argument("--port", type=int, default=8080, help="Port to listen on (default: 8080)")
|
|
165
|
+
p_serve.add_argument("--daemon", action="store_true", help="Run server as a detached background process")
|
|
166
|
+
p_serve.add_argument("--pid-file", default="mycode.pid", help="PID file path when running as daemon (default: mycode.pid)")
|
|
107
167
|
p_serve.set_defaults(func=cmd_serve)
|
|
108
168
|
|
|
169
|
+
p_stop = sub.add_parser("stop", help="Stop a running daemon server")
|
|
170
|
+
p_stop.add_argument("--pid-file", default="mycode.pid", help="PID file written by 'serve --daemon' (default: mycode.pid)")
|
|
171
|
+
p_stop.set_defaults(func=cmd_stop)
|
|
172
|
+
|
|
109
173
|
args = parser.parse_args()
|
|
110
174
|
args.func(args)
|
|
111
175
|
|
|
@@ -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.
|
|
3
|
+
Version: 0.4.1
|
|
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
|
|
@@ -93,7 +93,9 @@ MyCode delegates inference to a pluggable backend. Choose one based on what you
|
|
|
93
93
|
| Local LLM | `--backend llama` (default) | MCP server running at `localhost:8000` |
|
|
94
94
|
| Anthropic Claude | `--backend claude` | `ANTHROPIC_API_KEY` env var or `--api-key` |
|
|
95
95
|
| OpenAI | `--backend openai` | `OPENAI_API_KEY` env var or `--api-key` |
|
|
96
|
-
|
|
|
96
|
+
| MyCode server | `--backend mcp` | A running MyCode MCP server at `--mcp-url` |
|
|
97
|
+
|
|
98
|
+
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
99
|
|
|
98
100
|
### Setting up a local LLM
|
|
99
101
|
|
|
@@ -203,6 +205,11 @@ The profile is saved to `style_profile.json` by default. Specify a different pat
|
|
|
203
205
|
my-code analyze ./path/to/codebase --profile ./profiles/my_team.json
|
|
204
206
|
```
|
|
205
207
|
|
|
208
|
+
Delegating to a running MyCode server:
|
|
209
|
+
```bash
|
|
210
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./path/to/codebase
|
|
211
|
+
```
|
|
212
|
+
|
|
206
213
|
### Step 2 — Generate code
|
|
207
214
|
|
|
208
215
|
```bash
|
|
@@ -220,6 +227,9 @@ my-code --backend claude generate "write a binary search function"
|
|
|
220
227
|
|
|
221
228
|
# Override the model
|
|
222
229
|
my-code --backend claude --model claude-sonnet-4-6 generate "write a rate limiter"
|
|
230
|
+
|
|
231
|
+
# Delegate to a running MyCode server
|
|
232
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a rate limiter"
|
|
223
233
|
```
|
|
224
234
|
|
|
225
235
|
---
|
|
@@ -243,9 +253,14 @@ Commands:
|
|
|
243
253
|
|
|
244
254
|
generate TASK Generate code matching the saved style profile
|
|
245
255
|
|
|
246
|
-
serve
|
|
247
|
-
--host TEXT
|
|
248
|
-
--port INT
|
|
256
|
+
serve Start an MCP server (blocks until Ctrl-C)
|
|
257
|
+
--host TEXT Host to bind (default: 127.0.0.1)
|
|
258
|
+
--port INT Port to listen on (default: 8080)
|
|
259
|
+
--daemon Run as a detached background process
|
|
260
|
+
--pid-file TEXT PID file path for daemon mode (default: mycode.pid)
|
|
261
|
+
|
|
262
|
+
stop Stop a running daemon server
|
|
263
|
+
--pid-file TEXT PID file written by 'serve --daemon' (default: mycode.pid)
|
|
249
264
|
```
|
|
250
265
|
|
|
251
266
|
---
|
|
@@ -313,7 +328,7 @@ MyCode can expose itself as an MCP server so any MCP-compatible agent or orchest
|
|
|
313
328
|
### Quick start
|
|
314
329
|
|
|
315
330
|
```bash
|
|
316
|
-
# Local LLM backend (default)
|
|
331
|
+
# Local LLM backend (default) — foreground, blocks until Ctrl-C
|
|
317
332
|
my-code serve
|
|
318
333
|
|
|
319
334
|
# Claude backend
|
|
@@ -333,6 +348,50 @@ MyCode MCP server running at http://127.0.0.1:8080/mcp
|
|
|
333
348
|
Add to your MCP config: {"mycode": {"url": "http://127.0.0.1:8080/mcp"}}
|
|
334
349
|
```
|
|
335
350
|
|
|
351
|
+
### Running as a daemon
|
|
352
|
+
|
|
353
|
+
Add `--daemon` to run the server as a detached background process. The terminal returns immediately and the server keeps running.
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
my-code --backend claude serve --daemon
|
|
357
|
+
# → MyCode MCP server started as daemon (PID 12345) at http://127.0.0.1:8080/mcp
|
|
358
|
+
# → Stop with: my-code stop
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
The PID is written to `mycode.pid` by default. Stop the server with:
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
my-code stop
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
When running multiple instances on different ports, use `--pid-file` to keep them separate:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
my-code --backend claude serve --port 8080 --daemon --pid-file mycode-8080.pid
|
|
371
|
+
my-code --backend openai serve --port 8081 --daemon --pid-file mycode-8081.pid
|
|
372
|
+
|
|
373
|
+
my-code stop --pid-file mycode-8080.pid
|
|
374
|
+
my-code stop --pid-file mycode-8081.pid
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Using the server from the CLI
|
|
378
|
+
|
|
379
|
+
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.
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
# Start the server (uses a local LLM at port 8001 as its brain)
|
|
383
|
+
my-code --backend mcp --mcp-url http://localhost:8001/mcp serve --daemon --port 8000
|
|
384
|
+
|
|
385
|
+
# Analyze a codebase via the server
|
|
386
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp analyze ./my_project
|
|
387
|
+
|
|
388
|
+
# Generate code via the server (loads style_profile.json locally and sends it)
|
|
389
|
+
my-code --backend mcp --mcp-url http://localhost:8000/mcp generate "write a retry decorator"
|
|
390
|
+
|
|
391
|
+
# Stop the server
|
|
392
|
+
my-code stop
|
|
393
|
+
```
|
|
394
|
+
|
|
336
395
|
### Connecting from an MCP consumer
|
|
337
396
|
|
|
338
397
|
Add the printed snippet to your consumer's MCP config file (e.g. `.mcp.json`):
|
|
@@ -412,7 +471,7 @@ Both test suites use a `MockBackend` — no live AI backend required.
|
|
|
412
471
|
my_code/
|
|
413
472
|
├── analyzer.py # StyleAnalyzer — scans files, builds style profile
|
|
414
473
|
├── generator.py # generate_code() — formats prompt and calls backend
|
|
415
|
-
├── mcp_client.py #
|
|
474
|
+
├── mcp_client.py # MCPClient (raw LLM wrapper) + MyCodeClient (server delegation)
|
|
416
475
|
├── server.py # MCPServer — exposes analyze/generate as MCP tools
|
|
417
476
|
├── cli.py # CLI entry point (my-code command)
|
|
418
477
|
├── backends/
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mycode-aiagent"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.1"
|
|
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()
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|