mycode-aiagent 0.4.0__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.4.0 → mycode_aiagent-0.4.1}/PKG-INFO +31 -3
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/README.md +30 -2
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/cli.py +15 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/mcp_client.py +26 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/PKG-INFO +31 -3
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/SOURCES.txt +2 -1
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/pyproject.toml +1 -1
- mycode_aiagent-0.4.1/tests/test_server.py +138 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/__init__.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/__main__.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/analyzer.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/backends/__init__.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/backends/base.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/backends/claude_backend.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/backends/mcp_backend.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/backends/openai_backend.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/backends/ricky_backend.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/generator.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/server.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/utils/__init__.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/my_code/utils/prompts.py +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/dependency_links.txt +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/entry_points.txt +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/requires.txt +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/mycode_aiagent.egg-info/top_level.txt +0 -0
- {mycode_aiagent-0.4.0 → mycode_aiagent-0.4.1}/setup.cfg +0 -0
- {mycode_aiagent-0.4.0 → 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.4.
|
|
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
|
---
|
|
@@ -364,6 +374,24 @@ my-code stop --pid-file mycode-8080.pid
|
|
|
364
374
|
my-code stop --pid-file mycode-8081.pid
|
|
365
375
|
```
|
|
366
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
|
+
|
|
367
395
|
### Connecting from an MCP consumer
|
|
368
396
|
|
|
369
397
|
Add the printed snippet to your consumer's MCP config file (e.g. `.mcp.json`):
|
|
@@ -443,7 +471,7 @@ Both test suites use a `MockBackend` — no live AI backend required.
|
|
|
443
471
|
my_code/
|
|
444
472
|
├── analyzer.py # StyleAnalyzer — scans files, builds style profile
|
|
445
473
|
├── generator.py # generate_code() — formats prompt and calls backend
|
|
446
|
-
├── mcp_client.py #
|
|
474
|
+
├── mcp_client.py # MCPClient (raw LLM wrapper) + MyCodeClient (server delegation)
|
|
447
475
|
├── server.py # MCPServer — exposes analyze/generate as MCP tools
|
|
448
476
|
├── cli.py # CLI entry point (my-code command)
|
|
449
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
|
---
|
|
@@ -335,6 +345,24 @@ my-code stop --pid-file mycode-8080.pid
|
|
|
335
345
|
my-code stop --pid-file mycode-8081.pid
|
|
336
346
|
```
|
|
337
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
|
+
|
|
338
366
|
### Connecting from an MCP consumer
|
|
339
367
|
|
|
340
368
|
Add the printed snippet to your consumer's MCP config file (e.g. `.mcp.json`):
|
|
@@ -414,7 +442,7 @@ Both test suites use a `MockBackend` — no live AI backend required.
|
|
|
414
442
|
my_code/
|
|
415
443
|
├── analyzer.py # StyleAnalyzer — scans files, builds style profile
|
|
416
444
|
├── generator.py # generate_code() — formats prompt and calls backend
|
|
417
|
-
├── mcp_client.py #
|
|
445
|
+
├── mcp_client.py # MCPClient (raw LLM wrapper) + MyCodeClient (server delegation)
|
|
418
446
|
├── server.py # MCPServer — exposes analyze/generate as MCP tools
|
|
419
447
|
├── cli.py # CLI entry point (my-code command)
|
|
420
448
|
├── backends/
|
|
@@ -17,6 +17,7 @@ from pathlib import Path
|
|
|
17
17
|
from .analyzer import StyleAnalyzer
|
|
18
18
|
from .backends import make_backend
|
|
19
19
|
from .generator import generate_code
|
|
20
|
+
from .mcp_client import MyCodeClient
|
|
20
21
|
from .server import MCPServer
|
|
21
22
|
|
|
22
23
|
DEFAULT_PROFILE = Path("style_profile.json")
|
|
@@ -27,6 +28,13 @@ def cmd_analyze(args):
|
|
|
27
28
|
if not root.exists():
|
|
28
29
|
sys.exit(f"Error: directory not found: {root}")
|
|
29
30
|
|
|
31
|
+
if args.backend == "mcp":
|
|
32
|
+
client = MyCodeClient(args.mcp_url, args.timeout)
|
|
33
|
+
print(f"Analyzing codebase at {root} via MyCode server ...")
|
|
34
|
+
profile = client.analyze_codebase(str(root))
|
|
35
|
+
StyleAnalyzer.save_profile(profile, Path(args.profile))
|
|
36
|
+
return
|
|
37
|
+
|
|
30
38
|
backend = make_backend(
|
|
31
39
|
backend=args.backend,
|
|
32
40
|
api_key=args.api_key,
|
|
@@ -52,6 +60,13 @@ def cmd_generate(args):
|
|
|
52
60
|
"Run 'analyze' first."
|
|
53
61
|
)
|
|
54
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
|
+
|
|
55
70
|
backend = make_backend(
|
|
56
71
|
backend=args.backend,
|
|
57
72
|
api_key=args.api_key,
|
|
@@ -137,3 +137,29 @@ class MCPClient:
|
|
|
137
137
|
"arguments": {self._analyze_arg: prompt},
|
|
138
138
|
})
|
|
139
139
|
return self._extract_text(result)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class MyCodeClient(MCPClient):
|
|
143
|
+
"""Client for a running MyCode MCP server. Calls semantic tools directly."""
|
|
144
|
+
|
|
145
|
+
def __init__(self, url: str = "http://localhost:8000/mcp", timeout: int = 120):
|
|
146
|
+
self.url = url
|
|
147
|
+
self._timeout = timeout
|
|
148
|
+
self._session_id = None
|
|
149
|
+
self._initialize()
|
|
150
|
+
|
|
151
|
+
def analyze_codebase(self, path: str, save_to: str | None = None) -> dict:
|
|
152
|
+
args = {"path": path}
|
|
153
|
+
if save_to:
|
|
154
|
+
args["save_to"] = save_to
|
|
155
|
+
result = self._post("tools/call", {"name": "analyze_codebase", "arguments": args})
|
|
156
|
+
return json.loads(self._extract_text(result))
|
|
157
|
+
|
|
158
|
+
def generate_code(self, task: str, profile: dict | None = None, profile_path: str | None = None) -> str:
|
|
159
|
+
args = {"task": task}
|
|
160
|
+
if profile is not None:
|
|
161
|
+
args["profile"] = profile
|
|
162
|
+
elif profile_path:
|
|
163
|
+
args["profile_path"] = profile_path
|
|
164
|
+
result = self._post("tools/call", {"name": "generate_code", "arguments": args})
|
|
165
|
+
return self._extract_text(result)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mycode-aiagent
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.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
|
---
|
|
@@ -364,6 +374,24 @@ my-code stop --pid-file mycode-8080.pid
|
|
|
364
374
|
my-code stop --pid-file mycode-8081.pid
|
|
365
375
|
```
|
|
366
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
|
+
|
|
367
395
|
### Connecting from an MCP consumer
|
|
368
396
|
|
|
369
397
|
Add the printed snippet to your consumer's MCP config file (e.g. `.mcp.json`):
|
|
@@ -443,7 +471,7 @@ Both test suites use a `MockBackend` — no live AI backend required.
|
|
|
443
471
|
my_code/
|
|
444
472
|
├── analyzer.py # StyleAnalyzer — scans files, builds style profile
|
|
445
473
|
├── generator.py # generate_code() — formats prompt and calls backend
|
|
446
|
-
├── mcp_client.py #
|
|
474
|
+
├── mcp_client.py # MCPClient (raw LLM wrapper) + MyCodeClient (server delegation)
|
|
447
475
|
├── server.py # MCPServer — exposes analyze/generate as MCP tools
|
|
448
476
|
├── cli.py # CLI entry point (my-code command)
|
|
449
477
|
├── backends/
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mycode-aiagent"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.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
|