From 0f522e61c38942a718e997456f17bc04895fb35b Mon Sep 17 00:00:00 2001 From: NW Date: Sat, 9 May 2026 01:28:40 +0100 Subject: [PATCH] fix(gns-2): replace Basic Auth password with Bearer PAT for MCP --- .vscode/settings.json | 15 +++ MCP-STDIO-SETUP.md | 2 +- scripts/test-kilo-mcp-integration.py | 143 +++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 .vscode/settings.json create mode 100644 scripts/test-kilo-mcp-integration.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b065351 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "mcpServers": { + "forgejo-gitea": { + "command": "bun", + "args": [ + "/home/swp/Projects/APAW/scripts/mcp-gitea-stdio.cjs" + ], + "env": { + "FORGEJO_URL": "https://git.softuniq.eu", + "FORGEJO_TOKEN": "54822926dec114eaf1ef3ec5d7ff51c0e4ab40bf", + "LOG_LEVEL": "warn" + } + } + } +} diff --git a/MCP-STDIO-SETUP.md b/MCP-STDIO-SETUP.md index a892d13..5aed58a 100644 --- a/MCP-STDIO-SETUP.md +++ b/MCP-STDIO-SETUP.md @@ -46,7 +46,7 @@ bunx @ric_/forgejo-mcp --help # Запуск stdio сервера export FORGEJO_URL=https://git.softuniq.eu -export FORGEJO_TOKEN=your-token-here +export FORGEJO_TOKEN=54822926dec114eaf1ef3ec5d7ff51c0e4ab40bf echo '{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}' | bunx @ric_/forgejo-mcp ``` diff --git a/scripts/test-kilo-mcp-integration.py b/scripts/test-kilo-mcp-integration.py new file mode 100644 index 0000000..b096f85 --- /dev/null +++ b/scripts/test-kilo-mcp-integration.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +""" +test-kilo-mcp-integration.py +Тест интеграции MCP через mcp_settings.json (legacy Kilo Code path). +Проверяет конечную цепочку: mcp_settings.json → stdio bridge → forgejo-mcp → Gitea API +""" + +import json +import os +import subprocess +import sys + +MCP_SETTINGS_PATH = os.path.expanduser( + "~/.config/Code/User/globalStorage/kilocode.kilo-code/settings/mcp_settings.json" +) + +STDIO_SCRIPT = "/home/swp/Projects/APAW/scripts/mcp-gitea-stdio.cjs" + +def load_settings(): + if not os.path.exists(MCP_SETTINGS_PATH): + raise FileNotFoundError(f"MCP settings not found: {MCP_SETTINGS_PATH}") + with open(MCP_SETTINGS_PATH) as f: + return json.load(f) + +def validate_settings(data): + assert "mcpServers" in data, "Missing mcpServers key" + assert "forgejo-gitea" in data["mcpServers"], "Missing forgejo-gitea server" + srv = data["mcpServers"]["forgejo-gitea"] + assert "command" in srv, "Missing command" + assert "args" in srv, "Missing args" + assert "env" in srv, "Missing env" + print(f"✅ Settings valid: command={srv['command']}, args={srv['args']}") + return srv + +def test_stdio_rpc(server_config): + print("\n[1] Initialize stdio bridge...") + env = {**os.environ, **server_config.get("env", {})} + cmd = [server_config["command"]] + server_config["args"] + + proc = subprocess.Popen( + cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + env=env, + cwd="/home/swp/Projects/APAW", + ) + + def send(method, params=None, call_id=1): + req = json.dumps({"jsonrpc": "2.0", "method": method, "params": params or {}, "id": call_id}) + "\n" + proc.stdin.write(req) + proc.stdin.flush() + + def recv(): + line = proc.stdout.readline() + return json.loads(line) if line.strip() else None + + # initialize + send("initialize", { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": {"name": "test-kilo-mcp", "version": "1.0"} + }, 1) + resp = recv() + assert resp and "result" in resp, f"Initialize failed: {resp}" + print("✅ Initialize OK") + + # tools/list + send("tools/list", {}, 2) + resp = recv() + tools = resp.get("result", {}).get("tools", []) + assert len(tools) > 50, f"Expected >50 tools, got {len(tools)}" + print(f"✅ Tools: {len(tools)}") + + # get_issue #110 + print("\n[2] Call get_issue #110...") + send("tools/call", { + "name": "get_issue", + "arguments": {"owner": "UniqueSoft", "repo": "APAW", "index": 110} + }, 3) + resp = recv() + print(f" Raw resp keys: {list(resp.keys())}") + content_arr = resp.get("result", {}).get("content", []) + print(f" Content array len: {len(content_arr)}") + if content_arr: + content_text = content_arr[0].get("text", "") + print(f" Content text len: {len(content_text)}") + if not content_text: + # fallback: parse result directly + content_text = json.dumps(resp.get("result", {})) + else: + content_text = json.dumps(resp.get("result", {})) + assert content_text, "Empty content" + issue = json.loads(content_text) if content_text.startswith("{") else {"raw": content_text} + if "number" not in issue: + # direct result without wrapper + issue = resp.get("result", {}) + assert issue.get("number") == 110, f"Unexpected issue: {issue}" + print(f"✅ Issue #{issue['number']} - {issue.get('title', 'N/A')}") + + # checkpoint + print("\n[3] Verify checkpoint in body...") + assert "## GNS Checkpoint" in (issue.get("body") or ""), "No checkpoint" + print("✅ Checkpoint present") + + # budget/depth check + print("\n[4] Extract checkpoint YAML...") + body = issue.get("body", "") + import re + match = re.search(r"```yaml\n(.*?)\n```", body, re.S) + assert match, "No YAML block in issue body" + yaml_block = match.group(1) + assert "budget:" in yaml_block, "No budget in checkpoint" + assert "depth:" in yaml_block, "No depth in checkpoint" + print("✅ Budget and depth found in checkpoint") + + proc.stdin.close() + proc.wait(timeout=5) + print("✅ Stdio bridge closed cleanly") + +def main(): + print("=" * 60) + print("Kilo Code MCP Integration Test") + print("=" * 60) + print(f"Settings path: {MCP_SETTINGS_PATH}") + + try: + data = load_settings() + srv = validate_settings(data) + test_stdio_rpc(srv) + print("\n" + "=" * 60) + print("✅ ALL KILO MCP INTEGRATION TESTS PASSED") + print("=" * 60) + return 0 + except Exception as e: + print(f"\n❌ FAILED: {e}") + import traceback + traceback.print_exc() + return 1 + +if __name__ == "__main__": + sys.exit(main())