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.
Files changed (27) hide show
  1. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/PKG-INFO +66 -7
  2. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/README.md +65 -6
  3. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/cli.py +64 -0
  4. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/mcp_client.py +26 -0
  5. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/PKG-INFO +66 -7
  6. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/SOURCES.txt +2 -1
  7. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/pyproject.toml +1 -1
  8. mycode_aiagent-0.4.1/tests/test_server.py +138 -0
  9. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/__init__.py +0 -0
  10. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/__main__.py +0 -0
  11. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/analyzer.py +0 -0
  12. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/backends/__init__.py +0 -0
  13. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/backends/base.py +0 -0
  14. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/backends/claude_backend.py +0 -0
  15. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/backends/mcp_backend.py +0 -0
  16. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/backends/openai_backend.py +0 -0
  17. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/backends/ricky_backend.py +0 -0
  18. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/generator.py +0 -0
  19. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/server.py +0 -0
  20. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/utils/__init__.py +0 -0
  21. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/my_code/utils/prompts.py +0 -0
  22. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/dependency_links.txt +0 -0
  23. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/entry_points.txt +0 -0
  24. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/requires.txt +0 -0
  25. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/top_level.txt +0 -0
  26. {mycode_aiagent-0.3.1 → mycode_aiagent-0.4.1}/setup.cfg +0 -0
  27. {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.1
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
- | Custom MCP server | `--backend mcp` | Any MCP server at `--mcp-url` |
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 Start an MCP server (blocks until Ctrl-C)
247
- --host TEXT Host to bind (default: 127.0.0.1)
248
- --port INT Port to listen on (default: 8080)
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 # Generic MCP client (Streamable HTTP transport)
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
- | Custom MCP server | `--backend mcp` | Any MCP server at `--mcp-url` |
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 Start an MCP server (blocks until Ctrl-C)
218
- --host TEXT Host to bind (default: 127.0.0.1)
219
- --port INT Port to listen on (default: 8080)
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 # Generic MCP client (Streamable HTTP transport)
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.1
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
- | Custom MCP server | `--backend mcp` | Any MCP server at `--mcp-url` |
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 Start an MCP server (blocks until Ctrl-C)
247
- --host TEXT Host to bind (default: 127.0.0.1)
248
- --port INT Port to listen on (default: 8080)
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 # Generic MCP client (Streamable HTTP transport)
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/
@@ -21,4 +21,5 @@ mycode_aiagent.egg-info/dependency_links.txt
21
21
  mycode_aiagent.egg-info/entry_points.txt
22
22
  mycode_aiagent.egg-info/requires.txt
23
23
  mycode_aiagent.egg-info/top_level.txt
24
- tests/test_library.py
24
+ tests/test_library.py
25
+ 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.3.1"
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