feat(sdlc-rename): MCP-серверы sdlc-jira и sdlc-wiki, модуль в sdlc-hub mcp обязателен
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e34d61a483
commit
08460dc281
@ -17,8 +17,8 @@ def main(argv=None) -> int:
|
||||
p = sub.add_parser("serve", help="запустить HTTP API")
|
||||
p.add_argument("--host", default="0.0.0.0")
|
||||
p.add_argument("--port", type=int)
|
||||
p = sub.add_parser("mcp", help="MCP-сервер модуля по stdio (по умолчанию jira)")
|
||||
p.add_argument("module", nargs="?", default="jira", help="имя модуля; неизвестное — код 2 (проверка в mcp.run)")
|
||||
p = sub.add_parser("mcp", help="MCP-сервер модуля по stdio ")
|
||||
p.add_argument("module", nargs="?", default=None, help="имя модуля (jira, wiki); без модуля или неизвестный — код 2")
|
||||
p.add_argument("--project", help="метка проекта, к которому привязана сессия")
|
||||
for m in registry.modules():
|
||||
if m.cli_register:
|
||||
|
||||
@ -25,13 +25,10 @@ def unknown_project(label) -> str:
|
||||
return f"проект {label} не заведён в хабе: project_create или исправьте --project в .mcp.json"
|
||||
|
||||
|
||||
def mcp_json(label: str, module: str = registry.DEFAULT_AGENT) -> dict:
|
||||
"""Фрагмент .mcp.json для репозитория проекта: у модуля по умолчанию прежний (сервер jira-hub), у прочих модулей —
|
||||
сервер jira-hub-<модуль>."""
|
||||
run = ["run", "--project", str(HUB_ROOT), "sdlc-hub", "mcp"]
|
||||
if module == registry.DEFAULT_AGENT:
|
||||
return {"mcpServers": {"jira-hub": {"command": "uv", "args": [*run, "--project", label]}}}
|
||||
return {"mcpServers": {f"jira-hub-{module}": {"command": "uv", "args": [*run, module, "--project", label]}}}
|
||||
def mcp_json(label: str, module: str) -> dict:
|
||||
"""Фрагмент .mcp.json для репозитория проекта: сервер sdlc-<модуль>, запуск `sdlc-hub mcp <модуль>`."""
|
||||
run = ["run", "--project", str(HUB_ROOT), "sdlc-hub", "mcp", module, "--project", label]
|
||||
return {"mcpServers": {f"sdlc-{module}": {"command": "uv", "args": run}}}
|
||||
|
||||
|
||||
def client() -> HubClient:
|
||||
@ -199,9 +196,13 @@ def register_core_tools(mcp, *, projects_doc: str, columns: list[tuple[str, Call
|
||||
mcp.tool()(project_create)
|
||||
|
||||
|
||||
def run(module_name: str, project: str | None = None) -> int:
|
||||
def run(module_name: str | None, project: str | None = None) -> int:
|
||||
"""Запустить MCP-сервер модуля по stdio. Неизвестный модуль: список модулей в stderr, код 2, сервер не стартует."""
|
||||
global PROJECT
|
||||
if module_name is None:
|
||||
names = ", ".join(x.name for x in registry.modules() if x.mcp_server)
|
||||
print(f"укажите модуль: sdlc-hub mcp <модуль> [--project <метка>]; модули: {names}", file=sys.stderr)
|
||||
return 2
|
||||
m = registry.module(module_name)
|
||||
if m is None or m.mcp_server is None:
|
||||
names = ", ".join(x.name for x in registry.modules() if x.mcp_server)
|
||||
|
||||
@ -19,7 +19,7 @@ from .commands import describe, is_temp
|
||||
|
||||
POLL_SECONDS = 5
|
||||
BOTH_FILE_AND_PATH = "у ensure_attachment либо path, либо file"
|
||||
mcp = FastMCP("jira-hub")
|
||||
mcp = FastMCP("sdlc-jira")
|
||||
fmt_agent = core.fmt_agent
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
"""MCP-инструменты модуля wiki (stdio, FastMCP, сервер jira-hub-wiki) — инструменты сессии Claude поверх HTTP API хаба
|
||||
"""MCP-инструменты модуля wiki (stdio, FastMCP, сервер sdlc-wiki) — инструменты сессии Claude поверх HTTP API хаба
|
||||
(specs/mcp/spec.md «MCP вики»). Охрана, привязка к проекту и projects/project_create — в core/mcp.py. Markdown страниц и
|
||||
имена вложений приходят из Eva и считаются недоверенными: страница выдаётся текстом, имя файла сводится к basename."""
|
||||
import asyncio
|
||||
@ -16,7 +16,7 @@ ATTACHMENT_WAIT = 60
|
||||
SYNC_WAIT = 300
|
||||
CHUNK = 20000
|
||||
CANDIDATES = 10
|
||||
mcp = FastMCP("jira-hub-wiki")
|
||||
mcp = FastMCP("sdlc-wiki")
|
||||
|
||||
|
||||
def _project() -> dict:
|
||||
@ -242,7 +242,7 @@ def root_set(root: str) -> str:
|
||||
@core.guard
|
||||
def project_create(label: str, name: str, wiki_root: str = "") -> str:
|
||||
"""Завести проект (метка, название, необязательно корень вики: код DOC-… или ссылка Eva). С корнем хаб ставит
|
||||
синхронизацию; в ответе — фрагмент .mcp.json для репозитория проекта с сервером jira-hub-wiki."""
|
||||
синхронизацию; в ответе — фрагмент .mcp.json для репозитория проекта с сервером sdlc-wiki."""
|
||||
try:
|
||||
code = wiki_roots.parse_root(wiki_root)
|
||||
except ValueError as e:
|
||||
|
||||
@ -134,7 +134,7 @@
|
||||
"name": "projects"
|
||||
},
|
||||
{
|
||||
"description": "Завести проект (метка, название, необязательно корень вики: код DOC-… или ссылка Eva). С корнем хаб ставит\nсинхронизацию; в ответе — фрагмент .mcp.json для репозитория проекта с сервером jira-hub-wiki.",
|
||||
"description": "Завести проект (метка, название, необязательно корень вики: код DOC-… или ссылка Eva). С корнем хаб ставит\nсинхронизацию; в ответе — фрагмент .mcp.json для репозитория проекта с сервером sdlc-wiki.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"label": {
|
||||
|
||||
@ -66,19 +66,18 @@ def test_agent_publish_write_failure(tmp_path, monkeypatch, capsys):
|
||||
assert sorted(p.name for p in release.module_dir(paths.release_root(), "jira").iterdir()) == ["agent.py", "manifest.json"]
|
||||
|
||||
|
||||
def test_mcp_default_module(monkeypatch):
|
||||
def test_mcp_module_required(monkeypatch, capsys):
|
||||
from sdlc_hub.core import mcp as core_mcp
|
||||
from sdlc_hub.modules.jira import mcp as jira_mcp
|
||||
seen = []
|
||||
monkeypatch.setattr(core_mcp, "PROJECT", None)
|
||||
monkeypatch.setattr(jira_mcp.mcp, "run", lambda transport="stdio": seen.append((transport, core_mcp.PROJECT)))
|
||||
assert cli.main(["mcp", "--project", "AWPCA"]) == 0
|
||||
monkeypatch.setattr(core_mcp, "PROJECT", None) # run() пишет глобал; monkeypatch вернёт прежнее значение
|
||||
monkeypatch.setattr(jira_mcp.mcp, "run", lambda transport="stdio": seen.append(core_mcp.PROJECT))
|
||||
assert cli.main(["mcp", "--project", "AWPCA"]) == 2
|
||||
err = capsys.readouterr().err
|
||||
assert "укажите модуль: sdlc-hub mcp <модуль>" in err and "jira, wiki" in err
|
||||
assert seen == []
|
||||
assert cli.main(["mcp", "jira", "--project", "AWPCA"]) == 0
|
||||
assert seen == [("stdio", "AWPCA"), ("stdio", "AWPCA")]
|
||||
# без --project сессия не привязана к проекту
|
||||
monkeypatch.setattr(core_mcp, "PROJECT", "STALE")
|
||||
assert cli.main(["mcp"]) == 0
|
||||
assert seen[-1] == ("stdio", None)
|
||||
assert seen == ["AWPCA"]
|
||||
|
||||
|
||||
def test_mcp_unknown_module(monkeypatch, capsys):
|
||||
|
||||
@ -503,16 +503,20 @@ def test_project_create(live, monkeypatch): # сце
|
||||
out = jmcp.project_create("STOCKS_DRRK", "Запасы ДРРК", 5120)
|
||||
assert "STOCKS_DRRK" in out and re.search(r"пакет[^#]*#\d+", out) and "/mcp" in out
|
||||
frag = json.loads(out[out.index("{"):])
|
||||
assert frag == core_mcp.mcp_json("STOCKS_DRRK")
|
||||
assert frag["mcpServers"]["jira-hub"]["args"][-2:] == ["--project", "STOCKS_DRRK"]
|
||||
assert frag == core_mcp.mcp_json("STOCKS_DRRK", "jira")
|
||||
assert frag["mcpServers"]["sdlc-jira"]["args"][-2:] == ["--project", "STOCKS_DRRK"]
|
||||
assert live["client"].get("/batches")["batches"][0]["author"] == "claude"
|
||||
assert jmcp.project_create("awpca", "x").startswith("ошибка хаба 409")
|
||||
assert jmcp.project_create("BAD LABEL", "x").startswith("ошибка хаба 422")
|
||||
|
||||
|
||||
def test_server_name():
|
||||
assert jmcp.mcp.name == "sdlc-jira"
|
||||
|
||||
|
||||
def test_hub_root_is_repo():
|
||||
assert (core_mcp.HUB_ROOT / "pyproject.toml").is_file()
|
||||
assert core_mcp.mcp_json("X")["mcpServers"]["jira-hub"]["args"][:3] == ["run", "--project", str(core_mcp.HUB_ROOT)]
|
||||
assert core_mcp.mcp_json("X", "jira")["mcpServers"]["sdlc-jira"]["args"][:3] == ["run", "--project", str(core_mcp.HUB_ROOT)]
|
||||
|
||||
|
||||
def test_fmt_projects():
|
||||
|
||||
@ -150,11 +150,12 @@ def test_field_clashes_with_core(name):
|
||||
|
||||
|
||||
def test_mcp_json_per_module():
|
||||
first = core_mcp.mcp_json("AWPCA")["mcpServers"]
|
||||
assert list(first) == ["jira-hub"] and first["jira-hub"]["args"][-3:] == ["mcp", "--project", "AWPCA"]
|
||||
first = core_mcp.mcp_json("AWPCA", "jira")["mcpServers"]
|
||||
assert list(first) == ["sdlc-jira"] and first["sdlc-jira"]["args"][-4:] == ["mcp", "jira", "--project", "AWPCA"]
|
||||
assert first["sdlc-jira"]["args"][3] == "sdlc-hub"
|
||||
other = core_mcp.mcp_json("AWPCA", "wiki")["mcpServers"]
|
||||
assert list(other) == ["jira-hub-wiki"]
|
||||
assert other["jira-hub-wiki"]["args"][-4:] == ["mcp", "wiki", "--project", "AWPCA"]
|
||||
assert list(other) == ["sdlc-wiki"]
|
||||
assert other["sdlc-wiki"]["args"][-4:] == ["mcp", "wiki", "--project", "AWPCA"]
|
||||
|
||||
|
||||
def test_fmt_projects_columns():
|
||||
|
||||
@ -288,7 +288,7 @@ def test_jira_server_unchanged():
|
||||
|
||||
def test_run_wiki_module():
|
||||
m = registry.module("wiki")
|
||||
assert m.mcp_server().name == "jira-hub-wiki"
|
||||
assert m.mcp_server().name == "sdlc-wiki"
|
||||
|
||||
|
||||
def test_wiki_tool_names_order():
|
||||
@ -302,7 +302,7 @@ def test_projects_table_and_create(hub):
|
||||
text = str(out)
|
||||
assert "AWPCA" in text and "DOC-1" in text and "(текущий)" in text
|
||||
created = wmcp.project_create("n", "Н", "DOC-5")
|
||||
assert "jira-hub-wiki" in created
|
||||
assert "sdlc-wiki" in created
|
||||
assert fake.calls[-1][2]["wiki_root"] == "DOC-5"
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user