From 8c00683b792d14c033b52b6988e4ba2e76a2f261 Mon Sep 17 00:00:00 2001 From: kyson Date: Mon, 23 Mar 2026 12:38:11 +0800 Subject: [PATCH 1/2] feat(core): improve orchestrators, i18n instructions and config system --- pyproject.toml | 2 +- src/agent_tools/core/models/workflow.py | 10 +++- .../core/orchestrators/gh_pr_create.py | 2 +- .../core/orchestrators/gh_pr_merge.py | 2 +- .../core/orchestrators/git_commit.py | 9 ++- .../core/orchestrators/git_release.py | 8 +-- .../infrastructure/config/manager.py | 5 ++ .../config/resources/rules.yaml | 8 +++ .../config/resources/schema.json | 6 +- src/agent_tools/server/mcp_server.py | 56 +++++++++---------- 10 files changed, 64 insertions(+), 44 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4f6f81c..13e53fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "kyson-mcp-agent-tools" -version = "0.1.27" +version = "0.1.28" description = "Industrial-grade Agent Git Workflow Tools" readme = "README.md" requires-python = ">=3.13" diff --git a/src/agent_tools/core/models/workflow.py b/src/agent_tools/core/models/workflow.py index ba6615e..d28c1a2 100644 --- a/src/agent_tools/core/models/workflow.py +++ b/src/agent_tools/core/models/workflow.py @@ -17,9 +17,13 @@ class Result: def to_json(self) -> str: """Serialize result to JSON string.""" - # We no longer auto-prepend headers here to avoid redundancy. - # Orchestrators are responsible for high-quality instructions. - return json.dumps(asdict(self), indent=2, ensure_ascii=False) + + def serialize(obj): + if callable(obj): + return f"" + return str(obj) + + return json.dumps(asdict(self), default=serialize, indent=2, ensure_ascii=False) @property def ok(self) -> bool: diff --git a/src/agent_tools/core/orchestrators/gh_pr_create.py b/src/agent_tools/core/orchestrators/gh_pr_create.py index c4f73c2..2dfc147 100644 --- a/src/agent_tools/core/orchestrators/gh_pr_create.py +++ b/src/agent_tools/core/orchestrators/gh_pr_create.py @@ -112,7 +112,7 @@ def _handle_sense() -> Result: next_step="SYNTHESIZE_PR_DESCRIPTION", resume_point="create", instruction=( - "【STRICT PROTOCOL / 严格协议】您当前处于受控工作流中。禁止跳过步骤、禁止执行任何未授权的裸命令。\n" + "【STRICT PROTOCOL】You are in a controlled workflow. Skipping steps or executing unauthorized commands is strictly prohibited.\n" "【ACTION】\n" "1. Review 'commits' in details.\n" "2. Synthesize a professional PR title for `draft_json_str` following the 'details.json_format' layout.\n" diff --git a/src/agent_tools/core/orchestrators/gh_pr_merge.py b/src/agent_tools/core/orchestrators/gh_pr_merge.py index 2573d45..54c2dac 100644 --- a/src/agent_tools/core/orchestrators/gh_pr_merge.py +++ b/src/agent_tools/core/orchestrators/gh_pr_merge.py @@ -167,7 +167,7 @@ def _handle_sense() -> Result: next_step="SYNTHESIZE_SQUASH_MESSAGE", resume_point="merge", instruction=( - "【STRICT PROTOCOL / 严格协议】您当前处于受控工作流中。禁止跳过步骤、禁止执行任何未授权的裸命令。\n" + "【STRICT PROTOCOL】You are in a controlled workflow. Skipping steps or executing unauthorized commands is strictly prohibited.\n" "【ACTION】\n" "1. Review PR metadata in `details.pr`.\n" "2. Synthesize a professional squash commit message for `override_json_str` following the 'details.json_format' layout.\n" diff --git a/src/agent_tools/core/orchestrators/git_commit.py b/src/agent_tools/core/orchestrators/git_commit.py index 711b897..5b96f45 100644 --- a/src/agent_tools/core/orchestrators/git_commit.py +++ b/src/agent_tools/core/orchestrators/git_commit.py @@ -1,6 +1,5 @@ import json import logging -from dataclasses import asdict from typing import Literal from agent_tools.core.models.workflow import Result @@ -16,6 +15,7 @@ get_protected_branches, get_commit_grouping_signals, get_commit_max_groups, + get_sensitive_patterns, ) logger = logging.getLogger(__name__) @@ -59,7 +59,7 @@ def _handle_sense() -> Result: diff_info = get_diff_summary() # Check for sensitive files (e.g., .env, secrets) - sensitive_patterns = [".env", "key", "secret", "token", "password"] + sensitive_patterns = get_sensitive_patterns() risk_files = [ f.filepath for f in diff_info.changed_files if any(p in f.filepath.lower() for p in sensitive_patterns) ] @@ -81,7 +81,7 @@ def _handle_sense() -> Result: next_step="BUILD_COMMIT_PLAN", resume_point="commit", instruction=( - "【STRICT PROTOCOL / 严格协议】您当前处于受控工作流中。禁止直接提交,禁止绕过分析步骤。\n" + "【STRICT PROTOCOL】You are in a controlled workflow. Direct commits or bypassing analysis is strictly prohibited.\n" "【ACTION】\n" "1. Review 'staged' vs 'unstaged' files in details.\n" "2. Group files into logical commits (max: 'details.max_groups') using these signals: 'details.grouping_signals'.\n" @@ -108,11 +108,10 @@ def _handle_sense() -> Result: } ] }, - "subject_regex": get_commit_subject_regex, + "subject_regex": get_commit_subject_regex(), "body_wrap_length": get_commit_body_wrap_length(), "grouping_signals": get_commit_grouping_signals(), "max_groups": get_commit_max_groups(), - "branch_info": asdict(get_branch_context()), }, ) diff --git a/src/agent_tools/core/orchestrators/git_release.py b/src/agent_tools/core/orchestrators/git_release.py index 0b1eb79..ffeb9af 100644 --- a/src/agent_tools/core/orchestrators/git_release.py +++ b/src/agent_tools/core/orchestrators/git_release.py @@ -92,6 +92,7 @@ def _is_protected_branch() -> bool: def _handle_sense() -> Result: """Stage 1: Context gathering and planning.""" + branch_info = get_branch_context() latest_tag = get_latest_tag() if latest_tag: commits = get_commits_ahead(latest_tag) @@ -119,12 +120,12 @@ def _handle_sense() -> Result: next_step="PLAN_VERSION_BUMP", resume_point="release", instruction=( - "【STRICT PROTOCOL / 严格协议】\n" + "【STRICT PROTOCOL】You are in a controlled workflow. Direct commits or bypassing analysis is strictly prohibited.\n" "1. Analyze 'commits' in details to determine the next SemVer increment.\n" "2. Read versioning files to find current version. If already updated via recent merges, proceed to step 4.\n" "3. If version bump is needed (【PAUSE】You MUST propose the new version and its rationale, then WAIT for USER approval before proceeding):\n" - f" - **IF PROTECTED (is_protected={is_protected})**: Use 'gh_pr_create_flow' for a version PR. IMPORTANT: Wait for CI checks, then merge using 'gh_pr_merge_flow'.\n" - f" - **ELSE**: Update and commit directly using 'git_commit_flow'.\n" + f" - **IF PROTECTED (is_protected={is_protected})**: Create a task branch (e.g. release/vX.Y.Z), update version, commit via 'git_commit_flow', push, and then use 'gh_pr_create_flow' for a version PR. IMPORTANT: Wait for CI checks, then merge using 'gh_pr_merge_flow'.\n" + f" - **ELSE**: Update and commit directly to {branch_info.current_branch} using 'git_commit_flow'.\n" "4. Finalize with 'git_release_flow' (point='release'), with its 'tag_json_str' following the 'details.json_format'.\n" " - 'name': The chosen version string. 【STRICT】MUST match 'details.tag_regex'.\n" " - 'message': Categorized release notes. MUST follow the layout in 'details.json_format.message'.\n" @@ -148,7 +149,6 @@ def _handle_sense() -> Result: }, "tag_regex": get_release_tag_regex(), "body_wrap_length": get_commit_body_wrap_length(), - "branch_info": asdict(get_branch_context()), }, ) diff --git a/src/agent_tools/infrastructure/config/manager.py b/src/agent_tools/infrastructure/config/manager.py index 42ddcf3..a39f3ff 100644 --- a/src/agent_tools/infrastructure/config/manager.py +++ b/src/agent_tools/infrastructure/config/manager.py @@ -172,6 +172,11 @@ def get_allow_direct_actions_to_protected() -> bool: return cast(bool, rules["git"]["safety"]["allow_direct_actions_to_protected"]) +def get_sensitive_patterns() -> list[str]: + rules = load_rules() + return cast(list[str], rules["git"]["safety"]["sensitive_patterns"]) + + def get_release_tag_regex() -> str: rules = load_rules() return cast(str, rules["git"]["release"]["tag_regex"]) diff --git a/src/agent_tools/infrastructure/config/resources/rules.yaml b/src/agent_tools/infrastructure/config/resources/rules.yaml index 70e8edd..71d7b4d 100644 --- a/src/agent_tools/infrastructure/config/resources/rules.yaml +++ b/src/agent_tools/infrastructure/config/resources/rules.yaml @@ -46,6 +46,14 @@ git: # If false, the agent must refuse direct commits/pushes to the branches above allow_direct_actions_to_protected: false + # Patterns to identify sensitive files (e.g., secrets, tokens) + sensitive_patterns: + - ".env" + - "key" + - "secret" + - "token" + - "password" + # Performance and Token Limits for Diff Analysis diff: max_diff_lines_per_file: 500 diff --git a/src/agent_tools/infrastructure/config/resources/schema.json b/src/agent_tools/infrastructure/config/resources/schema.json index 80d9e21..dc15ab0 100644 --- a/src/agent_tools/infrastructure/config/resources/schema.json +++ b/src/agent_tools/infrastructure/config/resources/schema.json @@ -13,7 +13,7 @@ "safety": { "type": "object", "additionalProperties": false, - "required": ["protected_branches", "allow_direct_actions_to_protected"], + "required": ["protected_branches", "allow_direct_actions_to_protected", "sensitive_patterns"], "properties": { "protected_branches": { "type": "array", @@ -21,6 +21,10 @@ }, "allow_direct_actions_to_protected": { "type": "boolean" + }, + "sensitive_patterns": { + "type": "array", + "items": { "type": "string" } } } }, diff --git a/src/agent_tools/server/mcp_server.py b/src/agent_tools/server/mcp_server.py index e62c4db..5ff0805 100644 --- a/src/agent_tools/server/mcp_server.py +++ b/src/agent_tools/server/mcp_server.py @@ -23,30 +23,30 @@ async def _with_cwd(func: Callable, ctx: Context | None, *args, **kwargs): - """设置执行上下文的仓库路径。基于 v0.1.23 的验证,仅保留核心 IDE 感知机制。""" + """Set the repository path for the execution context. Based on v0.1.23 validation, only core IDE awareness is retained.""" final_path = None - # 核心:通过 IDE 获取真实物理路径 + # Core: Get the real physical path through the IDE if ctx: try: roots = await ctx.list_roots() if roots: - # 剔除 URI 协议头获得物理路径 + # Remove URI scheme to get physical path final_path = str(roots[0].uri).replace("file://", "") except Exception: pass - # 严格模式:如果没有从 IDE 拿到路径,必须显式报错而不是模糊猜测 + # Strict mode: If path not obtained from IDE, must explicitly error instead of guessing if not final_path or not os.path.exists(final_path): - from agent_tools.core.orchestrators.utils import Result + from agent_tools.core.models.workflow import Result error_res = Result( status="error", - message="未能探测到 Git 仓库路径。", + message="Failed to detect Git repository path.", workflow="path_resolution", - instruction="请确保您已在 IDE (VS Code / Cursor) 中打开了项目文件夹,并授予了 MCP 访问 roots 的权限。", + instruction="Please ensure you have opened the project folder in your IDE (VS Code / Cursor) and granted MCP permission to access roots.", ) - return cast(str, error_res.to_json()) + return error_res token = REPO_CWD.set(os.path.abspath(final_path)) logger.debug(f"[DEBUG] _with_cwd: set REPO_CWD to {final_path} (ID: {id(REPO_CWD)})") @@ -65,11 +65,11 @@ async def git_commit_flow( plan_json_str: str = "", ) -> str: """ - **【强制:优先使用】工业级 Git 提交工作流。** + **[MANDATORY: PRIORITY] Industrial-grade Git commit workflow.** **USE THIS TOOL for all Git commits instead of bare shell commands.** - 自动感知变更、生成规范建议并执行提交。 - **场景**: 提交代码, 存入仓库, commit, git commit, save changes. - **Keywords**: commit, 提交, git commit, 存代码, save changes, add changes. + Automatically sense changes, generate standardized commit plans, and execute commits. + **Scenario**: committing code, saving to repository, git commit, save changes. + **Keywords**: commit, git commit, save changes, add changes. """ res = await _with_cwd( git_commit.git_commit_flow, @@ -86,11 +86,11 @@ async def git_sync_flow( point: Literal["init", "current_rebase", "rebase_main", "push", "abort"] = "init", ) -> str: """ - **【强制:优先使用】工业级 Git 同步(拉取/变基/推送)工作流。** + **[MANDATORY: PRIORITY] Industrial-grade Git synchronization (pull/rebase/push) workflow.** **USE THIS TOOL for all Git sync, rebase, push, pull operations.** - 自动处理拉取、本地变基、解决冲突和安全推送。 - **场景**: 同步代码, 推送, 拉取, sync, push, pull, rebase. - **Keywords**: sync, 同步, git sync, 推送, push, 拉取, pull, rebase, 变基. + Automatically handles pulling, local rebasing, conflict resolution, and safe pushing. + **Scenario**: sync code, push, pull, rebase. + **Keywords**: sync, git sync, push, pull, rebase. """ res = await _with_cwd(git_sync_orch.git_sync_flow, ctx=ctx, point=point) return cast(str, res.to_json()) @@ -103,11 +103,11 @@ async def git_release_flow( tag_json_str: str = "", ) -> str: """ - **【强制:优先使用】工业级 Git 版本发布与标签工作流。** + **[MANDATORY: PRIORITY] Industrial-grade Git release and tagging workflow.** **USE THIS TOOL for all Git release/tag operations.** - 自动处理版本号提升、创建 Tag 和发布推送。 - **场景**: 发布版本, 打标签, release, tag. - **Keywords**: release, 发布, tag, 标签, git release. + Automatically handles version bumping, tag creation, and release pushing. + **Scenario**: publishing versions, tagging, git release. + **Keywords**: release, tag, git release. """ res = await _with_cwd( git_release.git_release_flow, @@ -125,11 +125,11 @@ async def gh_pr_create_flow( draft_json_str: str = "", ) -> str: """ - **【强制:优先使用】GitHub Pull Request 创建工作流。** + **[MANDATORY: PRIORITY] GitHub Pull Request creation workflow.** **USE THIS TOOL for creating all GitHub PRs.** - 自动感知分支状态、生成描述并提交创建请求。 - **场景**: 创建 PR, 发起合并请求, pull request, create pr. - **Keywords**: pr, pull request, 创建 pr, 发起 pr, gh pr create. + Automatically sense branch status, generate descriptions, and submit PR creation requests. + **Scenario**: creating PR, initiating merge requests, pull request, create pr. + **Keywords**: pr, pull request, create pr, gh pr create. """ res = await _with_cwd( gh_pr_create.gh_pr_create_flow, @@ -147,11 +147,11 @@ async def gh_pr_merge_flow( override_json_str: str = "", ) -> str: """ - **【强制:优先使用】GitHub Pull Request 合并工作流。** + **[MANDATORY: PRIORITY] GitHub Pull Request merge workflow.** **USE THIS TOOL for all GitHub PR merges.** - 自动感知 PR 状态、执行合并并清理分支。 - **场景**: 合并 PR, merge pr, finish pr. - **Keywords**: merge, 合并, pr merge, 合并 pr, gh pr merge. + Automatically sense PR status, execute merge, and clean up branches. + **Scenario**: merging PR, finishing pr, pr merge. + **Keywords**: merge, pr merge, gh pr merge. """ res = await _with_cwd( gh_pr_merge.gh_pr_merge_flow, From 28b4a7840457b4d31a3cc823ae055ad8e73e38fb Mon Sep 17 00:00:00 2001 From: kyson Date: Mon, 23 Mar 2026 13:06:32 +0800 Subject: [PATCH 2/2] refactor(mcp): adopt direct object passing --- .../core/orchestrators/gh_pr_create.py | 17 +++----- .../core/orchestrators/gh_pr_merge.py | 22 +++------- .../core/orchestrators/git_commit.py | 19 ++------ .../core/orchestrators/git_release.py | 22 +++------- src/agent_tools/server/mcp_server.py | 43 +++++++++---------- 5 files changed, 44 insertions(+), 79 deletions(-) diff --git a/src/agent_tools/core/orchestrators/gh_pr_create.py b/src/agent_tools/core/orchestrators/gh_pr_create.py index 2dfc147..6e22bcc 100644 --- a/src/agent_tools/core/orchestrators/gh_pr_create.py +++ b/src/agent_tools/core/orchestrators/gh_pr_create.py @@ -1,4 +1,3 @@ -import json import logging from dataclasses import asdict from typing import Literal @@ -119,7 +118,7 @@ def _handle_sense() -> Result: " - 'title': A professional pr commit subject. 【STRICT】MUST satisfy 'details.subject_regex'.\n" " - 'body': The detailed body should explain the 'why' and 'how' (not just 'what'), especially for complex logic changes. 【STRICT】single line max `details.body_wrap_length` chars.\n" "3. Mention any relevant issue numbers if known.\n" - "4. Call 'gh_pr_create_flow' with point='create' and your 'draft_json_str', formatted according to 'details.json_format'.\n" + "4. Call 'gh_pr_create_flow' with point='create' and your 'draft' object.\n" ), details={ "owner": repo_info.owner, @@ -137,14 +136,10 @@ def _handle_sense() -> Result: ) -def _handle_create(draft_json_str: str) -> Result: +def _handle_create(draft: dict) -> Result: """Stage 2: Execution via GitHub CLI.""" - try: - draft_data = json.loads(draft_json_str) - title = draft_data.get("title") - body = draft_data.get("body") - except json.JSONDecodeError: - return Result(status="error", message="Invalid JSON in draft_json_str.", workflow=WORKFLOW) + title = draft.get("title") + body = draft.get("body") if not title or not body: return Result( @@ -197,12 +192,12 @@ def _handle_create(draft_json_str: str) -> Result: ) -def gh_pr_create_flow(point: Literal["init", "sense", "create"] = "init", draft_json_str: str = "") -> Result: +def gh_pr_create_flow(point: Literal["init", "sense", "create"] = "init", draft: dict | None = None) -> Result: """Industrial-grade GitHub PR creation flow orchestrator.""" handlers = { "init": _handle_init, "sense": _handle_sense, - "create": lambda: _handle_create(draft_json_str), + "create": lambda: _handle_create(draft or {}), } try: diff --git a/src/agent_tools/core/orchestrators/gh_pr_merge.py b/src/agent_tools/core/orchestrators/gh_pr_merge.py index 54c2dac..e23b262 100644 --- a/src/agent_tools/core/orchestrators/gh_pr_merge.py +++ b/src/agent_tools/core/orchestrators/gh_pr_merge.py @@ -170,10 +170,10 @@ def _handle_sense() -> Result: "【STRICT PROTOCOL】You are in a controlled workflow. Skipping steps or executing unauthorized commands is strictly prohibited.\n" "【ACTION】\n" "1. Review PR metadata in `details.pr`.\n" - "2. Synthesize a professional squash commit message for `override_json_str` following the 'details.json_format' layout.\n" + "2. Synthesize a professional squash commit message for `override` following the 'details.json_format' layout.\n" " - 'title': A professional squash commit subject.【STRICT】MUST satisfy 'details.subject_regex'.\n" " - 'body': The detailed body should explain the 'why' and 'how' (not just 'what'), especially for complex logic changes. 【STRICT】single line max `details.body_wrap_length` chars.\n" - "3. Call 'gh_pr_merge_flow' with point='merge' and your 'override_json_str'.\n" + "3. Call 'gh_pr_merge_flow' with point='merge' and your 'override' object.\n" ), details={ "pr": pr_data, @@ -187,18 +187,10 @@ def _handle_sense() -> Result: ) -def _handle_merge(override_json_str: str) -> Result: +def _handle_merge(override: dict) -> Result: """Stage 2: Execution and local cleanup.""" - try: - data = json.loads(override_json_str) - title = data.get("title") - body = data.get("body") - except json.JSONDecodeError: - return Result( - status="error", - message="Invalid JSON in override_json_str.", - workflow=WORKFLOW, - ) + title = override.get("title") + body = override.get("body") # Validate regex subject_regex = get_commit_subject_regex() @@ -270,12 +262,12 @@ def _handle_merge(override_json_str: str) -> Result: return Result(status="success", message=cleanup_msg, workflow=WORKFLOW) -def gh_pr_merge_flow(point: Literal["init", "sense", "merge"] = "init", override_json_str: str = "") -> Result: +def gh_pr_merge_flow(point: Literal["init", "sense", "merge"] = "init", override: dict | None = None) -> Result: """Industrial-grade GitHub PR merging flow orchestrator.""" handlers = { "init": _handle_init, "sense": _handle_sense, - "merge": lambda: _handle_merge(override_json_str), + "merge": lambda: _handle_merge(override or {}), } try: diff --git a/src/agent_tools/core/orchestrators/git_commit.py b/src/agent_tools/core/orchestrators/git_commit.py index 5b96f45..d87a55e 100644 --- a/src/agent_tools/core/orchestrators/git_commit.py +++ b/src/agent_tools/core/orchestrators/git_commit.py @@ -1,4 +1,3 @@ -import json import logging from typing import Literal @@ -91,7 +90,7 @@ def _handle_sense() -> Result: " * Subject (1st line): 【STRICT】MUST satisfy 'details.subject_regex'.\n" " * Detail body (Add a blank line after the subject): " " * The detailed body should explain the 'why' and 'how' (not just 'what'), especially for complex logic changes. 【STRICT】single line max `details.body_wrap_length` chars.\n" - "4. Call 'git_commit_flow' with point='commit' and your 'plan_json_str'.\n" + "4. Call 'git_commit_flow' with point='commit' and your 'plan' object.\n" "【CONSTRAINTS】\n" "- Do NOT include files from 'risk_files' unless specifically authorized.\n" ), @@ -116,18 +115,8 @@ def _handle_sense() -> Result: ) -def _handle_commit(plan_json_str: str) -> Result: +def _handle_commit(plan: dict) -> Result: """Stage 2: Execute the provided commit plan with validation.""" - try: - plan = json.loads(plan_json_str) - except json.JSONDecodeError: - return Result( - status="handoff", - message="Invalid JSON format in plan.", - workflow=WORKFLOW, - resume_point="commit", - instruction="Fix the JSON formatting error and resubmit the plan.", - ) # execute_commit_plan handles 'git add' for files specified in the plan commit_res = execute_commit_plan(plan) @@ -147,11 +136,11 @@ def _handle_commit(plan_json_str: str) -> Result: ) -def git_commit_flow(point: Literal["sense", "commit"] = "sense", plan_json_str: str = "") -> Result: +def git_commit_flow(point: Literal["sense", "commit"] = "sense", plan: dict | None = None) -> Result: """Industrial-grade git commit flow orchestrator.""" handlers = { "sense": _handle_sense, - "commit": lambda: _handle_commit(plan_json_str), + "commit": lambda: _handle_commit(plan or {}), } try: diff --git a/src/agent_tools/core/orchestrators/git_release.py b/src/agent_tools/core/orchestrators/git_release.py index ffeb9af..dc4e42d 100644 --- a/src/agent_tools/core/orchestrators/git_release.py +++ b/src/agent_tools/core/orchestrators/git_release.py @@ -1,4 +1,3 @@ -import json import logging import re from dataclasses import asdict @@ -126,7 +125,7 @@ def _handle_sense() -> Result: "3. If version bump is needed (【PAUSE】You MUST propose the new version and its rationale, then WAIT for USER approval before proceeding):\n" f" - **IF PROTECTED (is_protected={is_protected})**: Create a task branch (e.g. release/vX.Y.Z), update version, commit via 'git_commit_flow', push, and then use 'gh_pr_create_flow' for a version PR. IMPORTANT: Wait for CI checks, then merge using 'gh_pr_merge_flow'.\n" f" - **ELSE**: Update and commit directly to {branch_info.current_branch} using 'git_commit_flow'.\n" - "4. Finalize with 'git_release_flow' (point='release'), with its 'tag_json_str' following the 'details.json_format'.\n" + "4. Finalize with 'git_release_flow' (point='release'), with its 'tag_data' following the 'details.json_format'.\n" " - 'name': The chosen version string. 【STRICT】MUST match 'details.tag_regex'.\n" " - 'message': Categorized release notes. MUST follow the layout in 'details.json_format.message'.\n" " * The field MUST include a header and categorized bullet points (Features, Bug Fixes, etc.).\n" @@ -153,19 +152,10 @@ def _handle_sense() -> Result: ) -def _handle_release(tag_json_str: str) -> Result: +def _handle_release(tag_data: dict) -> Result: """Stage 2: Physical tagging and atomic push.""" - try: - data = json.loads(tag_json_str) - name = data.get("name") - message = data.get("message") - except json.JSONDecodeError: - return Result( - status="error", - message="Invalid JSON in tag_json_str.", - workflow=WORKFLOW, - instruction="Fix the JSON format and retry.", - ) + name = tag_data.get("name") + message = tag_data.get("message") if not name or not message: return Result(status="error", message="Missing 'name' or 'message'.", workflow=WORKFLOW) @@ -205,12 +195,12 @@ def _handle_release(tag_json_str: str) -> Result: ) -def git_release_flow(point: Literal["init", "sense", "release"] = "init", tag_json_str: str = "") -> Result: +def git_release_flow(point: Literal["init", "sense", "release"] = "init", tag_data: dict | None = None) -> Result: """Industrial-grade git release flow orchestrator.""" handlers = { "init": _handle_init, "sense": _handle_sense, - "release": lambda: _handle_release(tag_json_str), + "release": lambda: _handle_release(tag_data or {}), } try: diff --git a/src/agent_tools/server/mcp_server.py b/src/agent_tools/server/mcp_server.py index 5ff0805..7d43b35 100644 --- a/src/agent_tools/server/mcp_server.py +++ b/src/agent_tools/server/mcp_server.py @@ -2,10 +2,11 @@ import os import sys from collections.abc import Callable -from typing import Literal, cast +from typing import Literal from fastmcp import Context, FastMCP +from agent_tools.core.models.workflow import Result from agent_tools.core.orchestrators import ( gh_pr_create, gh_pr_merge, @@ -22,7 +23,7 @@ mcp = FastMCP("agent-tools") -async def _with_cwd(func: Callable, ctx: Context | None, *args, **kwargs): +async def _with_cwd(func: Callable, ctx: Context | None, *args, **kwargs) -> Result: """Set the repository path for the execution context. Based on v0.1.23 validation, only core IDE awareness is retained.""" final_path = None @@ -38,8 +39,6 @@ async def _with_cwd(func: Callable, ctx: Context | None, *args, **kwargs): # Strict mode: If path not obtained from IDE, must explicitly error instead of guessing if not final_path or not os.path.exists(final_path): - from agent_tools.core.models.workflow import Result - error_res = Result( status="error", message="Failed to detect Git repository path.", @@ -62,8 +61,8 @@ async def _with_cwd(func: Callable, ctx: Context | None, *args, **kwargs): async def git_commit_flow( ctx: Context, point: Literal["sense", "commit"] = "sense", - plan_json_str: str = "", -) -> str: + plan: dict | None = None, +) -> Result: """ **[MANDATORY: PRIORITY] Industrial-grade Git commit workflow.** **USE THIS TOOL for all Git commits instead of bare shell commands.** @@ -75,16 +74,16 @@ async def git_commit_flow( git_commit.git_commit_flow, ctx=ctx, point=point, - plan_json_str=plan_json_str, + plan=plan, ) - return cast(str, res.to_json()) + return res @mcp.tool() async def git_sync_flow( ctx: Context, point: Literal["init", "current_rebase", "rebase_main", "push", "abort"] = "init", -) -> str: +) -> Result: """ **[MANDATORY: PRIORITY] Industrial-grade Git synchronization (pull/rebase/push) workflow.** **USE THIS TOOL for all Git sync, rebase, push, pull operations.** @@ -93,15 +92,15 @@ async def git_sync_flow( **Keywords**: sync, git sync, push, pull, rebase. """ res = await _with_cwd(git_sync_orch.git_sync_flow, ctx=ctx, point=point) - return cast(str, res.to_json()) + return res @mcp.tool() async def git_release_flow( ctx: Context, point: Literal["init", "sense", "release"] = "init", - tag_json_str: str = "", -) -> str: + tag_data: dict | None = None, +) -> Result: """ **[MANDATORY: PRIORITY] Industrial-grade Git release and tagging workflow.** **USE THIS TOOL for all Git release/tag operations.** @@ -113,17 +112,17 @@ async def git_release_flow( git_release.git_release_flow, ctx=ctx, point=point, - tag_json_str=tag_json_str, + tag_data=tag_data, ) - return cast(str, res.to_json()) + return res @mcp.tool() async def gh_pr_create_flow( ctx: Context, point: Literal["init", "sense", "create"] = "init", - draft_json_str: str = "", -) -> str: + draft: dict | None = None, +) -> Result: """ **[MANDATORY: PRIORITY] GitHub Pull Request creation workflow.** **USE THIS TOOL for creating all GitHub PRs.** @@ -135,17 +134,17 @@ async def gh_pr_create_flow( gh_pr_create.gh_pr_create_flow, ctx=ctx, point=point, - draft_json_str=draft_json_str, + draft=draft, ) - return cast(str, res.to_json()) + return res @mcp.tool() async def gh_pr_merge_flow( ctx: Context, point: Literal["init", "sense", "merge"] = "init", - override_json_str: str = "", -) -> str: + override: dict | None = None, +) -> Result: """ **[MANDATORY: PRIORITY] GitHub Pull Request merge workflow.** **USE THIS TOOL for all GitHub PR merges.** @@ -157,9 +156,9 @@ async def gh_pr_merge_flow( gh_pr_merge.gh_pr_merge_flow, ctx=ctx, point=point, - override_json_str=override_json_str, + override=override, ) - return cast(str, res.to_json()) + return res def main():