mycode-aiagent 0.2.1__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/PKG-INFO +87 -4
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/README.md +86 -3
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/__init__.py +10 -1
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/cli.py +18 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/mcp_client.py +18 -1
- mycode_aiagent-0.3.0/my_code/server.py +166 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/mycode_aiagent.egg-info/PKG-INFO +87 -4
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/mycode_aiagent.egg-info/SOURCES.txt +1 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/pyproject.toml +1 -1
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/__main__.py +0 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/analyzer.py +0 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/backends/__init__.py +0 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/backends/base.py +0 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/backends/claude_backend.py +0 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/backends/mcp_backend.py +0 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/backends/openai_backend.py +0 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/backends/ricky_backend.py +0 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/generator.py +0 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/utils/__init__.py +0 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/utils/prompts.py +0 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/mycode_aiagent.egg-info/dependency_links.txt +0 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/mycode_aiagent.egg-info/entry_points.txt +0 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/mycode_aiagent.egg-info/requires.txt +0 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/mycode_aiagent.egg-info/top_level.txt +0 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/setup.cfg +0 -0
- {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/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.3.0
|
|
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
|
|
@@ -242,6 +242,10 @@ Commands:
|
|
|
242
242
|
--verbose Print each file as it is analyzed
|
|
243
243
|
|
|
244
244
|
generate TASK Generate code matching the saved style profile
|
|
245
|
+
|
|
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)
|
|
245
249
|
```
|
|
246
250
|
|
|
247
251
|
---
|
|
@@ -302,6 +306,77 @@ code = generate_code("write a logging helper", backend, profile)
|
|
|
302
306
|
|
|
303
307
|
---
|
|
304
308
|
|
|
309
|
+
## Running as an MCP Server
|
|
310
|
+
|
|
311
|
+
MyCode can expose itself as an MCP server so any MCP-compatible agent or orchestrator can call its tools directly — no MCP knowledge required.
|
|
312
|
+
|
|
313
|
+
### Quick start
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# Local LLM backend (default)
|
|
317
|
+
my-code serve
|
|
318
|
+
|
|
319
|
+
# Claude backend
|
|
320
|
+
my-code --backend claude serve --port 8080
|
|
321
|
+
|
|
322
|
+
# OpenAI backend
|
|
323
|
+
my-code --backend openai serve --port 8080
|
|
324
|
+
|
|
325
|
+
# Bind on all interfaces
|
|
326
|
+
my-code --backend claude serve --host 0.0.0.0 --port 8080
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
On startup the server prints the URL and the config snippet to paste:
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
MyCode MCP server running at http://127.0.0.1:8080/mcp
|
|
333
|
+
Add to your MCP config: {"mycode": {"url": "http://127.0.0.1:8080/mcp"}}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Connecting from an MCP consumer
|
|
337
|
+
|
|
338
|
+
Add the printed snippet to your consumer's MCP config file (e.g. `.mcp.json`):
|
|
339
|
+
|
|
340
|
+
```json
|
|
341
|
+
{
|
|
342
|
+
"mycode": { "url": "http://127.0.0.1:8080/mcp" }
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Tools exposed
|
|
347
|
+
|
|
348
|
+
| Tool | Required args | Optional args |
|
|
349
|
+
|------|--------------|---------------|
|
|
350
|
+
| `analyze_codebase` | `path` — directory to analyze | `save_to` — path to save the profile JSON |
|
|
351
|
+
| `generate_code` | `task` — what to write | `profile` — inline profile object; `profile_path` — path to a saved profile (default: `style_profile.json`) |
|
|
352
|
+
|
|
353
|
+
Both tools return plain text. `analyze_codebase` returns the style profile as a JSON string. `generate_code` returns the generated source code.
|
|
354
|
+
|
|
355
|
+
### Programmatic usage
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
from my_code import run_server, make_backend
|
|
359
|
+
|
|
360
|
+
# Blocking — call from a background thread if needed
|
|
361
|
+
run_server(backend=make_backend("claude"), host="127.0.0.1", port=8080)
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Or use `MCPServer` directly for more control:
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
from my_code import MCPServer, make_backend
|
|
368
|
+
import threading
|
|
369
|
+
|
|
370
|
+
server = MCPServer(make_backend("claude"), host="127.0.0.1", port=8080)
|
|
371
|
+
httpd = server.start() # binds immediately
|
|
372
|
+
port = httpd.server_address[1] # actual port (useful when port=0)
|
|
373
|
+
t = threading.Thread(target=httpd.serve_forever, daemon=True)
|
|
374
|
+
t.start()
|
|
375
|
+
# ... httpd.shutdown() to stop
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
305
380
|
## Deep Analysis
|
|
306
381
|
|
|
307
382
|
For a richer style profile, `scripts/deep_analyze.py` runs six focused queries (naming, error handling, string formatting, module structure, docstrings, and representative snippets) and synthesizes them into a single detailed profile.
|
|
@@ -318,10 +393,16 @@ This is slower than the standard `analyze` command but produces a more detailed
|
|
|
318
393
|
## Running Tests
|
|
319
394
|
|
|
320
395
|
```bash
|
|
396
|
+
# Library smoke tests (analyze → generate pipeline)
|
|
321
397
|
python tests/test_library.py
|
|
398
|
+
|
|
399
|
+
# MCP server protocol tests
|
|
400
|
+
python -m pytest tests/test_server.py -v
|
|
401
|
+
# or
|
|
402
|
+
python -m unittest tests/test_server.py
|
|
322
403
|
```
|
|
323
404
|
|
|
324
|
-
|
|
405
|
+
Both test suites use a `MockBackend` — no live AI backend required.
|
|
325
406
|
|
|
326
407
|
---
|
|
327
408
|
|
|
@@ -332,17 +413,19 @@ my_code/
|
|
|
332
413
|
├── analyzer.py # StyleAnalyzer — scans files, builds style profile
|
|
333
414
|
├── generator.py # generate_code() — formats prompt and calls backend
|
|
334
415
|
├── mcp_client.py # Generic MCP client (Streamable HTTP transport)
|
|
416
|
+
├── server.py # MCPServer — exposes analyze/generate as MCP tools
|
|
335
417
|
├── cli.py # CLI entry point (my-code command)
|
|
336
418
|
├── backends/
|
|
337
419
|
│ ├── base.py # AIBackend abstract base class
|
|
338
420
|
│ ├── ricky_backend.py # Local LLM backend (connects via MCP)
|
|
339
421
|
│ ├── claude_backend.py
|
|
340
422
|
│ ├── openai_backend.py
|
|
341
|
-
│ └── mcp_backend.py #
|
|
423
|
+
│ └── mcp_backend.py # Generic MCP server backend
|
|
342
424
|
└── utils/
|
|
343
425
|
└── prompts.py # Prompt templates for extraction, summary, generation
|
|
344
426
|
scripts/
|
|
345
427
|
└── deep_analyze.py # Multi-query deep style analysis
|
|
346
428
|
tests/
|
|
347
|
-
|
|
429
|
+
├── test_library.py # Smoke tests for analyze/generate (no live backend)
|
|
430
|
+
└── test_server.py # MCP server protocol tests (no live backend)
|
|
348
431
|
```
|
|
@@ -213,6 +213,10 @@ Commands:
|
|
|
213
213
|
--verbose Print each file as it is analyzed
|
|
214
214
|
|
|
215
215
|
generate TASK Generate code matching the saved style profile
|
|
216
|
+
|
|
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)
|
|
216
220
|
```
|
|
217
221
|
|
|
218
222
|
---
|
|
@@ -273,6 +277,77 @@ code = generate_code("write a logging helper", backend, profile)
|
|
|
273
277
|
|
|
274
278
|
---
|
|
275
279
|
|
|
280
|
+
## Running as an MCP Server
|
|
281
|
+
|
|
282
|
+
MyCode can expose itself as an MCP server so any MCP-compatible agent or orchestrator can call its tools directly — no MCP knowledge required.
|
|
283
|
+
|
|
284
|
+
### Quick start
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
# Local LLM backend (default)
|
|
288
|
+
my-code serve
|
|
289
|
+
|
|
290
|
+
# Claude backend
|
|
291
|
+
my-code --backend claude serve --port 8080
|
|
292
|
+
|
|
293
|
+
# OpenAI backend
|
|
294
|
+
my-code --backend openai serve --port 8080
|
|
295
|
+
|
|
296
|
+
# Bind on all interfaces
|
|
297
|
+
my-code --backend claude serve --host 0.0.0.0 --port 8080
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
On startup the server prints the URL and the config snippet to paste:
|
|
301
|
+
|
|
302
|
+
```
|
|
303
|
+
MyCode MCP server running at http://127.0.0.1:8080/mcp
|
|
304
|
+
Add to your MCP config: {"mycode": {"url": "http://127.0.0.1:8080/mcp"}}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Connecting from an MCP consumer
|
|
308
|
+
|
|
309
|
+
Add the printed snippet to your consumer's MCP config file (e.g. `.mcp.json`):
|
|
310
|
+
|
|
311
|
+
```json
|
|
312
|
+
{
|
|
313
|
+
"mycode": { "url": "http://127.0.0.1:8080/mcp" }
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Tools exposed
|
|
318
|
+
|
|
319
|
+
| Tool | Required args | Optional args |
|
|
320
|
+
|------|--------------|---------------|
|
|
321
|
+
| `analyze_codebase` | `path` — directory to analyze | `save_to` — path to save the profile JSON |
|
|
322
|
+
| `generate_code` | `task` — what to write | `profile` — inline profile object; `profile_path` — path to a saved profile (default: `style_profile.json`) |
|
|
323
|
+
|
|
324
|
+
Both tools return plain text. `analyze_codebase` returns the style profile as a JSON string. `generate_code` returns the generated source code.
|
|
325
|
+
|
|
326
|
+
### Programmatic usage
|
|
327
|
+
|
|
328
|
+
```python
|
|
329
|
+
from my_code import run_server, make_backend
|
|
330
|
+
|
|
331
|
+
# Blocking — call from a background thread if needed
|
|
332
|
+
run_server(backend=make_backend("claude"), host="127.0.0.1", port=8080)
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Or use `MCPServer` directly for more control:
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
from my_code import MCPServer, make_backend
|
|
339
|
+
import threading
|
|
340
|
+
|
|
341
|
+
server = MCPServer(make_backend("claude"), host="127.0.0.1", port=8080)
|
|
342
|
+
httpd = server.start() # binds immediately
|
|
343
|
+
port = httpd.server_address[1] # actual port (useful when port=0)
|
|
344
|
+
t = threading.Thread(target=httpd.serve_forever, daemon=True)
|
|
345
|
+
t.start()
|
|
346
|
+
# ... httpd.shutdown() to stop
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
276
351
|
## Deep Analysis
|
|
277
352
|
|
|
278
353
|
For a richer style profile, `scripts/deep_analyze.py` runs six focused queries (naming, error handling, string formatting, module structure, docstrings, and representative snippets) and synthesizes them into a single detailed profile.
|
|
@@ -289,10 +364,16 @@ This is slower than the standard `analyze` command but produces a more detailed
|
|
|
289
364
|
## Running Tests
|
|
290
365
|
|
|
291
366
|
```bash
|
|
367
|
+
# Library smoke tests (analyze → generate pipeline)
|
|
292
368
|
python tests/test_library.py
|
|
369
|
+
|
|
370
|
+
# MCP server protocol tests
|
|
371
|
+
python -m pytest tests/test_server.py -v
|
|
372
|
+
# or
|
|
373
|
+
python -m unittest tests/test_server.py
|
|
293
374
|
```
|
|
294
375
|
|
|
295
|
-
|
|
376
|
+
Both test suites use a `MockBackend` — no live AI backend required.
|
|
296
377
|
|
|
297
378
|
---
|
|
298
379
|
|
|
@@ -303,17 +384,19 @@ my_code/
|
|
|
303
384
|
├── analyzer.py # StyleAnalyzer — scans files, builds style profile
|
|
304
385
|
├── generator.py # generate_code() — formats prompt and calls backend
|
|
305
386
|
├── mcp_client.py # Generic MCP client (Streamable HTTP transport)
|
|
387
|
+
├── server.py # MCPServer — exposes analyze/generate as MCP tools
|
|
306
388
|
├── cli.py # CLI entry point (my-code command)
|
|
307
389
|
├── backends/
|
|
308
390
|
│ ├── base.py # AIBackend abstract base class
|
|
309
391
|
│ ├── ricky_backend.py # Local LLM backend (connects via MCP)
|
|
310
392
|
│ ├── claude_backend.py
|
|
311
393
|
│ ├── openai_backend.py
|
|
312
|
-
│ └── mcp_backend.py #
|
|
394
|
+
│ └── mcp_backend.py # Generic MCP server backend
|
|
313
395
|
└── utils/
|
|
314
396
|
└── prompts.py # Prompt templates for extraction, summary, generation
|
|
315
397
|
scripts/
|
|
316
398
|
└── deep_analyze.py # Multi-query deep style analysis
|
|
317
399
|
tests/
|
|
318
|
-
|
|
400
|
+
├── test_library.py # Smoke tests for analyze/generate (no live backend)
|
|
401
|
+
└── test_server.py # MCP server protocol tests (no live backend)
|
|
319
402
|
```
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.3.0"
|
|
2
2
|
|
|
3
3
|
from .analyzer import StyleAnalyzer
|
|
4
4
|
from .generator import generate_code
|
|
5
5
|
from .backends import AIBackend, ClaudeBackend, OpenAIBackend, RickyBackend, MCPBackend, make_backend
|
|
6
|
+
from .server import MCPServer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run_server(backend=None, host: str = "127.0.0.1", port: int = 8080):
|
|
10
|
+
"""Start a blocking MCP server exposing analyze_codebase and generate_code as tools."""
|
|
11
|
+
MCPServer(backend or make_backend(), host=host, port=port).run()
|
|
12
|
+
|
|
6
13
|
|
|
7
14
|
__all__ = [
|
|
8
15
|
"StyleAnalyzer",
|
|
@@ -13,4 +20,6 @@ __all__ = [
|
|
|
13
20
|
"RickyBackend",
|
|
14
21
|
"MCPBackend",
|
|
15
22
|
"make_backend",
|
|
23
|
+
"MCPServer",
|
|
24
|
+
"run_server",
|
|
16
25
|
]
|
|
@@ -14,6 +14,7 @@ from pathlib import Path
|
|
|
14
14
|
from .analyzer import StyleAnalyzer
|
|
15
15
|
from .backends import make_backend
|
|
16
16
|
from .generator import generate_code
|
|
17
|
+
from .server import MCPServer
|
|
17
18
|
|
|
18
19
|
DEFAULT_PROFILE = Path("style_profile.json")
|
|
19
20
|
|
|
@@ -64,6 +65,18 @@ def cmd_generate(args):
|
|
|
64
65
|
print(code)
|
|
65
66
|
|
|
66
67
|
|
|
68
|
+
def cmd_serve(args):
|
|
69
|
+
backend = make_backend(
|
|
70
|
+
backend=args.backend,
|
|
71
|
+
api_key=args.api_key,
|
|
72
|
+
ricky_url=args.ricky_url,
|
|
73
|
+
mcp_url=args.mcp_url,
|
|
74
|
+
model=args.model,
|
|
75
|
+
timeout=args.timeout,
|
|
76
|
+
)
|
|
77
|
+
MCPServer(backend, host=args.host, port=args.port, default_profile=args.profile).run()
|
|
78
|
+
|
|
79
|
+
|
|
67
80
|
def main():
|
|
68
81
|
parser = argparse.ArgumentParser(description="Style-aware code agent")
|
|
69
82
|
parser.add_argument(
|
|
@@ -88,6 +101,11 @@ def main():
|
|
|
88
101
|
p_gen.add_argument("task", help="Description of what to write")
|
|
89
102
|
p_gen.set_defaults(func=cmd_generate)
|
|
90
103
|
|
|
104
|
+
p_serve = sub.add_parser("serve", help="Start an MCP server exposing analyze and generate as tools")
|
|
105
|
+
p_serve.add_argument("--host", default="127.0.0.1", help="Host to bind (default: 127.0.0.1)")
|
|
106
|
+
p_serve.add_argument("--port", type=int, default=8080, help="Port to listen on (default: 8080)")
|
|
107
|
+
p_serve.set_defaults(func=cmd_serve)
|
|
108
|
+
|
|
91
109
|
args = parser.parse_args()
|
|
92
110
|
args.func(args)
|
|
93
111
|
|
|
@@ -104,9 +104,26 @@ class MCPClient:
|
|
|
104
104
|
if isinstance(content, list):
|
|
105
105
|
for block in content:
|
|
106
106
|
if block.get("type") == "text":
|
|
107
|
-
return block.get("text", "")
|
|
107
|
+
return self._unwrap_llm_response(block.get("text", ""))
|
|
108
108
|
return str(content)
|
|
109
109
|
|
|
110
|
+
@staticmethod
|
|
111
|
+
def _unwrap_llm_response(text: str) -> str:
|
|
112
|
+
try:
|
|
113
|
+
data = json.loads(text)
|
|
114
|
+
if isinstance(data, dict) and "choices" in data:
|
|
115
|
+
choices = data["choices"]
|
|
116
|
+
if choices:
|
|
117
|
+
choice = choices[0]
|
|
118
|
+
if "text" in choice:
|
|
119
|
+
return choice["text"]
|
|
120
|
+
content = choice.get("message", {}).get("content")
|
|
121
|
+
if content:
|
|
122
|
+
return content
|
|
123
|
+
except (json.JSONDecodeError, KeyError, IndexError):
|
|
124
|
+
pass
|
|
125
|
+
return text
|
|
126
|
+
|
|
110
127
|
def ask_for_code(self, prompt: str) -> str:
|
|
111
128
|
result = self._post("tools/call", {
|
|
112
129
|
"name": self._tool_name,
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import uuid
|
|
3
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .analyzer import StyleAnalyzer
|
|
7
|
+
from .generator import generate_code as _generate_code
|
|
8
|
+
|
|
9
|
+
_PROTOCOL = "2024-11-05"
|
|
10
|
+
|
|
11
|
+
_TOOLS = [
|
|
12
|
+
{
|
|
13
|
+
"name": "analyze_codebase",
|
|
14
|
+
"description": "Analyze a codebase directory and extract its coding style profile.",
|
|
15
|
+
"inputSchema": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"properties": {
|
|
18
|
+
"path": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "Path to the codebase directory to analyze.",
|
|
21
|
+
},
|
|
22
|
+
"save_to": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "Optional path to save the resulting profile JSON file.",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
"required": ["path"],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "generate_code",
|
|
32
|
+
"description": "Generate Python code that matches a saved style profile.",
|
|
33
|
+
"inputSchema": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"properties": {
|
|
36
|
+
"task": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "Description of what code to write.",
|
|
39
|
+
},
|
|
40
|
+
"profile": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"description": "Inline style profile object — takes precedence over profile_path.",
|
|
43
|
+
},
|
|
44
|
+
"profile_path": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "Path to a saved style_profile.json (default: style_profile.json).",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
"required": ["task"],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class _MCPError(Exception):
|
|
56
|
+
def __init__(self, code: int, message: str):
|
|
57
|
+
self.code = code
|
|
58
|
+
self.message = message
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class _MCPRequestHandler(BaseHTTPRequestHandler):
|
|
62
|
+
def do_POST(self):
|
|
63
|
+
if self.path != "/mcp":
|
|
64
|
+
self._send_sse({"jsonrpc": "2.0", "id": None, "error": {"code": -32600, "message": "Not found"}}, 404)
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
length = int(self.headers.get("Content-Length", 0))
|
|
68
|
+
body = self.rfile.read(length)
|
|
69
|
+
try:
|
|
70
|
+
msg = json.loads(body)
|
|
71
|
+
except json.JSONDecodeError:
|
|
72
|
+
self._send_sse({"jsonrpc": "2.0", "id": None, "error": {"code": -32700, "message": "Parse error"}}, 400)
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
req_id = msg.get("id")
|
|
76
|
+
method = msg.get("method", "")
|
|
77
|
+
params = msg.get("params", {})
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
result, extra_headers = self.server.mcp._handle(method, params)
|
|
81
|
+
self._send_sse({"jsonrpc": "2.0", "id": req_id, "result": result}, 200, extra_headers)
|
|
82
|
+
except _MCPError as e:
|
|
83
|
+
self._send_sse({"jsonrpc": "2.0", "id": req_id, "error": {"code": e.code, "message": e.message}}, 200)
|
|
84
|
+
except Exception as e:
|
|
85
|
+
self._send_sse({"jsonrpc": "2.0", "id": req_id, "error": {"code": -32603, "message": str(e)}}, 200)
|
|
86
|
+
|
|
87
|
+
def _send_sse(self, data: dict, status: int = 200, extra_headers: dict | None = None):
|
|
88
|
+
body = f"data: {json.dumps(data)}\n\n".encode()
|
|
89
|
+
self.send_response(status)
|
|
90
|
+
self.send_header("Content-Type", "text/event-stream")
|
|
91
|
+
self.send_header("Content-Length", str(len(body)))
|
|
92
|
+
if extra_headers:
|
|
93
|
+
for k, v in extra_headers.items():
|
|
94
|
+
self.send_header(k, v)
|
|
95
|
+
self.end_headers()
|
|
96
|
+
self.wfile.write(body)
|
|
97
|
+
|
|
98
|
+
def log_message(self, format, *args):
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class MCPServer:
|
|
103
|
+
def __init__(self, backend, host: str = "127.0.0.1", port: int = 8080, default_profile: str = "style_profile.json"):
|
|
104
|
+
self._backend = backend
|
|
105
|
+
self._host = host
|
|
106
|
+
self._port = port
|
|
107
|
+
self._default_profile = default_profile
|
|
108
|
+
|
|
109
|
+
def _handle(self, method: str, params: dict) -> tuple[dict, dict | None]:
|
|
110
|
+
if method == "initialize":
|
|
111
|
+
return self._initialize(), {"Mcp-Session-Id": str(uuid.uuid4())}
|
|
112
|
+
if method == "tools/list":
|
|
113
|
+
return {"tools": _TOOLS}, None
|
|
114
|
+
if method == "tools/call":
|
|
115
|
+
name = params.get("name", "")
|
|
116
|
+
arguments = params.get("arguments", {})
|
|
117
|
+
text = self._call_tool(name, arguments)
|
|
118
|
+
return {"content": [{"type": "text", "text": text}]}, None
|
|
119
|
+
raise _MCPError(-32601, f"Method not found: {method}")
|
|
120
|
+
|
|
121
|
+
def _initialize(self) -> dict:
|
|
122
|
+
return {
|
|
123
|
+
"protocolVersion": _PROTOCOL,
|
|
124
|
+
"capabilities": {"tools": {}},
|
|
125
|
+
"serverInfo": {"name": "mycode", "version": "0.3.0"},
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
def _call_tool(self, name: str, args: dict) -> str:
|
|
129
|
+
if name == "analyze_codebase":
|
|
130
|
+
return self._analyze(args)
|
|
131
|
+
if name == "generate_code":
|
|
132
|
+
return self._generate(args)
|
|
133
|
+
raise _MCPError(-32602, f"Unknown tool: {name}")
|
|
134
|
+
|
|
135
|
+
def _analyze(self, args: dict) -> str:
|
|
136
|
+
path = Path(args["path"]).resolve()
|
|
137
|
+
profile = StyleAnalyzer(self._backend).analyze_codebase(path)
|
|
138
|
+
save_to = args.get("save_to")
|
|
139
|
+
if save_to:
|
|
140
|
+
StyleAnalyzer.save_profile(profile, Path(save_to))
|
|
141
|
+
return json.dumps(profile, indent=2)
|
|
142
|
+
|
|
143
|
+
def _generate(self, args: dict) -> str:
|
|
144
|
+
task = args["task"]
|
|
145
|
+
profile = args.get("profile")
|
|
146
|
+
if profile is None:
|
|
147
|
+
profile_path = Path(args.get("profile_path") or self._default_profile)
|
|
148
|
+
profile = StyleAnalyzer.load_profile(profile_path)
|
|
149
|
+
return _generate_code(task=task, backend=self._backend, profile=profile)
|
|
150
|
+
|
|
151
|
+
def start(self) -> ThreadingHTTPServer:
|
|
152
|
+
httpd = ThreadingHTTPServer((self._host, self._port), _MCPRequestHandler)
|
|
153
|
+
httpd.mcp = self
|
|
154
|
+
return httpd
|
|
155
|
+
|
|
156
|
+
def run(self):
|
|
157
|
+
httpd = self.start()
|
|
158
|
+
host, port = httpd.server_address
|
|
159
|
+
print(f"MyCode MCP server running at http://{host}:{port}/mcp")
|
|
160
|
+
print(f'Add to your MCP config: {{"mycode": {{"url": "http://{host}:{port}/mcp"}}}}')
|
|
161
|
+
try:
|
|
162
|
+
httpd.serve_forever()
|
|
163
|
+
except KeyboardInterrupt:
|
|
164
|
+
pass
|
|
165
|
+
finally:
|
|
166
|
+
httpd.server_close()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mycode-aiagent
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
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
|
|
@@ -242,6 +242,10 @@ Commands:
|
|
|
242
242
|
--verbose Print each file as it is analyzed
|
|
243
243
|
|
|
244
244
|
generate TASK Generate code matching the saved style profile
|
|
245
|
+
|
|
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)
|
|
245
249
|
```
|
|
246
250
|
|
|
247
251
|
---
|
|
@@ -302,6 +306,77 @@ code = generate_code("write a logging helper", backend, profile)
|
|
|
302
306
|
|
|
303
307
|
---
|
|
304
308
|
|
|
309
|
+
## Running as an MCP Server
|
|
310
|
+
|
|
311
|
+
MyCode can expose itself as an MCP server so any MCP-compatible agent or orchestrator can call its tools directly — no MCP knowledge required.
|
|
312
|
+
|
|
313
|
+
### Quick start
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# Local LLM backend (default)
|
|
317
|
+
my-code serve
|
|
318
|
+
|
|
319
|
+
# Claude backend
|
|
320
|
+
my-code --backend claude serve --port 8080
|
|
321
|
+
|
|
322
|
+
# OpenAI backend
|
|
323
|
+
my-code --backend openai serve --port 8080
|
|
324
|
+
|
|
325
|
+
# Bind on all interfaces
|
|
326
|
+
my-code --backend claude serve --host 0.0.0.0 --port 8080
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
On startup the server prints the URL and the config snippet to paste:
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
MyCode MCP server running at http://127.0.0.1:8080/mcp
|
|
333
|
+
Add to your MCP config: {"mycode": {"url": "http://127.0.0.1:8080/mcp"}}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Connecting from an MCP consumer
|
|
337
|
+
|
|
338
|
+
Add the printed snippet to your consumer's MCP config file (e.g. `.mcp.json`):
|
|
339
|
+
|
|
340
|
+
```json
|
|
341
|
+
{
|
|
342
|
+
"mycode": { "url": "http://127.0.0.1:8080/mcp" }
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Tools exposed
|
|
347
|
+
|
|
348
|
+
| Tool | Required args | Optional args |
|
|
349
|
+
|------|--------------|---------------|
|
|
350
|
+
| `analyze_codebase` | `path` — directory to analyze | `save_to` — path to save the profile JSON |
|
|
351
|
+
| `generate_code` | `task` — what to write | `profile` — inline profile object; `profile_path` — path to a saved profile (default: `style_profile.json`) |
|
|
352
|
+
|
|
353
|
+
Both tools return plain text. `analyze_codebase` returns the style profile as a JSON string. `generate_code` returns the generated source code.
|
|
354
|
+
|
|
355
|
+
### Programmatic usage
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
from my_code import run_server, make_backend
|
|
359
|
+
|
|
360
|
+
# Blocking — call from a background thread if needed
|
|
361
|
+
run_server(backend=make_backend("claude"), host="127.0.0.1", port=8080)
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Or use `MCPServer` directly for more control:
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
from my_code import MCPServer, make_backend
|
|
368
|
+
import threading
|
|
369
|
+
|
|
370
|
+
server = MCPServer(make_backend("claude"), host="127.0.0.1", port=8080)
|
|
371
|
+
httpd = server.start() # binds immediately
|
|
372
|
+
port = httpd.server_address[1] # actual port (useful when port=0)
|
|
373
|
+
t = threading.Thread(target=httpd.serve_forever, daemon=True)
|
|
374
|
+
t.start()
|
|
375
|
+
# ... httpd.shutdown() to stop
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
305
380
|
## Deep Analysis
|
|
306
381
|
|
|
307
382
|
For a richer style profile, `scripts/deep_analyze.py` runs six focused queries (naming, error handling, string formatting, module structure, docstrings, and representative snippets) and synthesizes them into a single detailed profile.
|
|
@@ -318,10 +393,16 @@ This is slower than the standard `analyze` command but produces a more detailed
|
|
|
318
393
|
## Running Tests
|
|
319
394
|
|
|
320
395
|
```bash
|
|
396
|
+
# Library smoke tests (analyze → generate pipeline)
|
|
321
397
|
python tests/test_library.py
|
|
398
|
+
|
|
399
|
+
# MCP server protocol tests
|
|
400
|
+
python -m pytest tests/test_server.py -v
|
|
401
|
+
# or
|
|
402
|
+
python -m unittest tests/test_server.py
|
|
322
403
|
```
|
|
323
404
|
|
|
324
|
-
|
|
405
|
+
Both test suites use a `MockBackend` — no live AI backend required.
|
|
325
406
|
|
|
326
407
|
---
|
|
327
408
|
|
|
@@ -332,17 +413,19 @@ my_code/
|
|
|
332
413
|
├── analyzer.py # StyleAnalyzer — scans files, builds style profile
|
|
333
414
|
├── generator.py # generate_code() — formats prompt and calls backend
|
|
334
415
|
├── mcp_client.py # Generic MCP client (Streamable HTTP transport)
|
|
416
|
+
├── server.py # MCPServer — exposes analyze/generate as MCP tools
|
|
335
417
|
├── cli.py # CLI entry point (my-code command)
|
|
336
418
|
├── backends/
|
|
337
419
|
│ ├── base.py # AIBackend abstract base class
|
|
338
420
|
│ ├── ricky_backend.py # Local LLM backend (connects via MCP)
|
|
339
421
|
│ ├── claude_backend.py
|
|
340
422
|
│ ├── openai_backend.py
|
|
341
|
-
│ └── mcp_backend.py #
|
|
423
|
+
│ └── mcp_backend.py # Generic MCP server backend
|
|
342
424
|
└── utils/
|
|
343
425
|
└── prompts.py # Prompt templates for extraction, summary, generation
|
|
344
426
|
scripts/
|
|
345
427
|
└── deep_analyze.py # Multi-query deep style analysis
|
|
346
428
|
tests/
|
|
347
|
-
|
|
429
|
+
├── test_library.py # Smoke tests for analyze/generate (no live backend)
|
|
430
|
+
└── test_server.py # MCP server protocol tests (no live backend)
|
|
348
431
|
```
|
|
@@ -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.3.0"
|
|
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"
|
|
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
|