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.
Files changed (26) hide show
  1. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/PKG-INFO +87 -4
  2. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/README.md +86 -3
  3. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/__init__.py +10 -1
  4. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/cli.py +18 -0
  5. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/mcp_client.py +18 -1
  6. mycode_aiagent-0.3.0/my_code/server.py +166 -0
  7. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/mycode_aiagent.egg-info/PKG-INFO +87 -4
  8. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/mycode_aiagent.egg-info/SOURCES.txt +1 -0
  9. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/pyproject.toml +1 -1
  10. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/__main__.py +0 -0
  11. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/analyzer.py +0 -0
  12. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/backends/__init__.py +0 -0
  13. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/backends/base.py +0 -0
  14. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/backends/claude_backend.py +0 -0
  15. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/backends/mcp_backend.py +0 -0
  16. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/backends/openai_backend.py +0 -0
  17. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/backends/ricky_backend.py +0 -0
  18. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/generator.py +0 -0
  19. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/utils/__init__.py +0 -0
  20. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/my_code/utils/prompts.py +0 -0
  21. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/mycode_aiagent.egg-info/dependency_links.txt +0 -0
  22. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/mycode_aiagent.egg-info/entry_points.txt +0 -0
  23. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/mycode_aiagent.egg-info/requires.txt +0 -0
  24. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/mycode_aiagent.egg-info/top_level.txt +0 -0
  25. {mycode_aiagent-0.2.1 → mycode_aiagent-0.3.0}/setup.cfg +0 -0
  26. {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.2.1
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
- The test suite uses a `MockBackend` so no live AI backend is required. It exercises the full analyze → generate pipeline.
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 # Placeholder for custom MCP backends
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
- └── test_library.py # Smoke tests (no live backend required)
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
- The test suite uses a `MockBackend` so no live AI backend is required. It exercises the full analyze → generate pipeline.
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 # Placeholder for custom MCP backends
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
- └── test_library.py # Smoke tests (no live backend required)
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.5"
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.2.1
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
- The test suite uses a `MockBackend` so no live AI backend is required. It exercises the full analyze → generate pipeline.
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 # Placeholder for custom MCP backends
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
- └── test_library.py # Smoke tests (no live backend required)
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
  ```
@@ -6,6 +6,7 @@ my_code/analyzer.py
6
6
  my_code/cli.py
7
7
  my_code/generator.py
8
8
  my_code/mcp_client.py
9
+ my_code/server.py
9
10
  my_code/backends/__init__.py
10
11
  my_code/backends/base.py
11
12
  my_code/backends/claude_backend.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mycode-aiagent"
7
- version = "0.2.1"
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