import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import type { McpTextResponse } from './types.js'; import { buildBlockerResolution } from './tools/blocker.js'; import { defaultHistoryRoots, taskTypeValues } from './core/constants.js'; import { createCleanupProcessPlan, cleanupHistoryArtifacts } from './tools/cleanup.js'; import { buildEnvPolicy, scanTaskRisk } from './tools/envRisk.js'; import { buildGuardrails } from './tools/guardrails.js'; import { inspectAllProjects, inspectProject } from './tools/inspect.js'; import { buildWorkflowLoopAudit } from './tools/loop.js'; import { readObsidianContext, syncObsidianWorkflow, validateObsidianVault } from './tools/obsidian.js'; import { buildGlobalCodeReview, defaultReviewProjects } from './tools/review.js'; import { appendTaskRecord, createCommitChecklist, prepareTask, readWorkflowContext } from './tools/task.js'; import { buildBusinessTestPlan, buildNapcatDeviceProfileCheck, createApiTestPlan, createPageTestCase, } from './tools/testing.js'; import { buildVerificationPlan } from './tools/verification.js'; import { buildWorkstreamCloseout } from './tools/closeout.js'; import { buildChangeDocSync } from './tools/docSync.js'; import { buildDeployObservation } from './tools/deployObservation.js'; import { buildNapcatRemoteDevHandoff, buildNapcatRuntimeReleaseReadiness, buildNapcatSyncCandidateReview, buildNapcatUpstreamAudit, buildNasCodexBootstrapPlan, napcatAutomationDefaults, } from './tools/napcatAutomation.js'; import { buildCommitPlan, buildComponentWorkflow, buildDbSyncPlan, buildFinishTask, buildPushPlan, buildRemoteHealthCheck } from './tools/workflow.js'; import { workstreamReusablePatternValues } from './core/constants.js'; export function response(data: unknown): McpTextResponse { return { content: [ { text: typeof data === 'string' ? data : JSON.stringify(data, null, 2), type: 'text', }, ], }; } /** * Registers every ktWorkflow MCP tool against the provided MCP server instance. * @param server - MCP SDK server that owns tool names, schemas, descriptions, and async handlers. * @returns Nothing; handlers are attached to `server` for later stdio transport use. */ export function registerTools(server: McpServer): void { server.registerTool( 'kt_read_context', { description: '读取 KT 根目录紧凑上下文,并返回项目清单和最近任务记录;需要完整文档时显式 includeFullDocs=true。', inputSchema: { includeFullDocs: z.boolean().default(false), taskRecordCount: z.number().int().min(0).max(20).default(3), }, title: 'Read KT Workflow Context', }, async (input) => response(readWorkflowContext(input)), ); server.registerTool( 'kt_inspect_project', { description: '检查 KT 子项目的仓库类型、包管理器、Node 版本、env 文件和 Git 状态。', inputSchema: { includeGit: z.boolean().default(true), project: z.string().default('root'), }, title: 'Inspect KT Project', }, async (input) => response(await inspectProject(input)), ); server.registerTool( 'kt_inspect_all_projects', { description: '一次性检查 KT 工作区所有已知项目,适合多仓库联动任务开工前使用。', inputSchema: { includeGit: z.boolean().default(true), includeRoot: z.boolean().default(true), }, title: 'Inspect All KT Projects', }, async (input) => response(await inspectAllProjects(input)), ); server.registerTool( 'kt_guardrails', { description: '按任务类型生成 KT 防偏差清单,包含开工、改动、禁止项和验证约束。', inputSchema: { changeType: z.enum(taskTypeValues).optional(), includePageTest: z.boolean().default(false), paths: z.array(z.string()).default([]), project: z.string().default('root'), taskType: z.enum(taskTypeValues).default('general'), userRequest: z.string().optional(), }, title: 'KT Guardrails', }, async (input) => response(buildGuardrails(input)), ); server.registerTool( 'kt_prepare_task', { description: '生成一份 KT 任务 work packet:上下文、项目检查、防偏差清单、风险扫描和验证计划。', inputSchema: { changedFiles: z.array(z.string()).default([]), entryUrl: z.string().optional(), includeGit: z.boolean().default(true), includePageTest: z.boolean().default(false), paths: z.array(z.string()).default([]), project: z.string().default('root'), recordTitle: z.string().optional(), taskType: z.enum(taskTypeValues).default('general'), userRequest: z.string().optional(), }, title: 'Prepare KT Task', }, async (input) => response(await prepareTask(input)), ); server.registerTool( 'kt_suggest_verification', { description: '按 KT 规则为指定项目和变更类型生成轻量验证命令与注意事项。', inputSchema: { changeType: z.enum(taskTypeValues).default('general'), includePageTest: z.boolean().default(false), project: z.string().default('root'), }, title: 'Suggest KT Verification', }, async (input) => response(buildVerificationPlan(input)), ); server.registerTool( 'kt_create_page_test_case', { description: '生成 KT 页面级可视化测试用例骨架,包含事不过三闭环规则。', inputSchema: { account: z.string().optional(), assertions: z.array(z.string()).default([]), entryUrl: z.string().optional(), project: z.string().default('admin'), steps: z.array(z.string()).default([]), title: z.string().optional(), }, title: 'Create KT Page Test Case', }, async (input) => response(createPageTestCase(input)), ); server.registerTool( 'kt_api_test_plan', { description: '为后端接口改动生成真实调用测试计划和响应结构断言。', inputSchema: { auth: z.enum(['none', 'bearer']).default('bearer'), baseUrl: z.string().default('http://localhost:3000'), body: z.record(z.any()).optional(), contentType: z.boolean().default(true), endpoint: z.string().default('/api/待填写'), expectedFields: z.array(z.string()).default([]), method: z.enum(['DELETE', 'GET', 'PATCH', 'POST', 'PUT']).default('GET'), project: z.string().default('api'), }, title: 'KT API Test Plan', }, async (input) => response(createApiTestPlan(input)), ); server.registerTool( 'kt_cleanup_history', { description: '清理 KT 运行态历史产物。默认 dryRun=true,只预览;执行时按 LastWriteTime 每个 .kt-workspace 历史目录仅保留最近 3 轮;可复用模板和参考资产应迁入 workspace-assets。', inputSchema: { dryRun: z.boolean().default(true), keep: z.number().int().min(0).max(50).default(3), roots: z.array(z.string()).default([...defaultHistoryRoots]), }, title: 'KT Cleanup History Artifacts', }, async (input) => response(cleanupHistoryArtifacts(input)), ); server.registerTool( 'kt_cleanup_process_plan', { description: '生成按项目路径和端口检查验证进程的 Debian Bash 只读命令,不直接杀进程。', inputSchema: { ports: z.array(z.number().int().min(1).max(65_535)).default([]), project: z.string().default('root'), }, title: 'KT Cleanup Process Plan', }, async (input) => response(createCleanupProcessPlan(input)), ); server.registerTool( 'kt_env_policy', { description: '检查指定项目 env 文件现状,并输出 KT 环境配置提交策略。', inputSchema: { project: z.string().default('root'), }, title: 'KT Env Policy', }, async (input) => response(buildEnvPolicy(input)), ); server.registerTool( 'kt_risk_scan', { description: '按当前变更文件或传入文件列表扫描常见偏差风险和验证提醒。', inputSchema: { changedFiles: z.array(z.string()).default([]), project: z.string().default('root'), }, title: 'KT Risk Scan', }, async (input) => response(await scanTaskRisk(input)), ); server.registerTool( 'kt_global_code_review', { description: '对 KT 已知子仓库做只读全局 CodeReview 扫描:Git 状态、tracked env/deploy/secrets、冲突标记、调试输出、疑似凭据字面量和当前变更风险。默认只对变更文件做内容扫描,避免历史误报污染上下文。', inputSchema: { contentScanMode: z.enum(['changed', 'all']).default('changed'), includeContentScan: z.boolean().default(true), includeRootScan: z.boolean().default(true), maxFindingsPerProject: z.number().int().min(1).max(200).default(20), projects: z.array(z.string()).default([...defaultReviewProjects]), }, title: 'KT Global Code Review', }, async (input) => response(await buildGlobalCodeReview(input)), ); server.registerTool( 'kt_finish_task', { description: '一键生成任务收尾包:项目状态、验证计划、可选验证执行、全局 review、历史产物清理预览/执行和停止条件。', inputSchema: { changeType: z.enum(taskTypeValues).default('general'), cleanupHistory: z.boolean().default(false), contentScanMode: z.enum(['changed', 'all']).default('changed'), includeRootScan: z.boolean().default(true), keepHistory: z.number().int().min(0).max(50).default(3), maxFindingsPerProject: z.number().int().min(1).max(200).default(20), projects: z.array(z.string()).default([...defaultReviewProjects]), runValidation: z.boolean().default(false), }, title: 'KT Finish Task', }, async (input) => response(await buildFinishTask(input)), ); server.registerTool( 'kt_workstream_closeout', { description: '生成大方向完结闭环审计:开发、测试证据、KT 全局 review、问题记录、稳定解法和 ktWorkflow 升级是否齐备。', inputSchema: { changedProjects: z.array(z.string()).default([]), cleanupEvidence: z.array(z.string()).default([]), cleanupFinalDeleted: z.number().int().min(0).optional(), docSyncEvidence: z.array(z.string()).default([]), ktWorkflowUpdated: z.boolean().default(false), problemRecords: z.array(z.string()).default([]), reviewEvidence: z.array(z.string()).default([]), reusablePatterns: z.array(z.enum(workstreamReusablePatternValues)).default(['none']), stableSolutions: z.array(z.string()).default([]), title: z.string().optional(), upgradeNotes: z.string().optional(), verificationEvidence: z.array(z.string()).default([]), }, title: 'KT Workstream Closeout', }, async (input) => response(buildWorkstreamCloseout(input)), ); server.registerTool( 'kt_workflow_loop_audit', { description: '审计 KT 自动化闭环:上下文锚定、测试用例、验证证据、历史清理、问题固化、ktWorkflow 升级和 KT 全局 review 是否齐备。', inputSchema: { changedProjects: z.array(z.string()).default([]), cleanupExecuted: z.boolean().default(false), cleanupEvidence: z.array(z.string()).default([]), cleanupFinalDeleted: z.number().int().min(0).optional(), cleanupPreviewDeleted: z.number().int().min(0).optional(), docSyncEvidence: z.array(z.string()).default([]), failureRecords: z.array(z.string()).default([]), ktWorkflowUpdated: z.boolean().default(false), problemRecords: z.array(z.string()).default([]), requireCompletion: z.boolean().default(true), reusablePattern: z.boolean().default(false), reviewEvidence: z.array(z.string()).default([]), stableSolutions: z.array(z.string()).default([]), stage: z.enum(['failure', 'finish', 'start', 'test']).default('finish'), taskTitle: z.string().optional(), testCases: z.array(z.string()).default([]), verificationEvidence: z.array(z.string()).default([]), }, title: 'KT Workflow Loop Audit', }, async (input) => response(buildWorkflowLoopAudit(input)), ); server.registerTool( 'kt_change_doc_sync', { description: '根据代码改动路径生成必须检查/同步的 README、API、AGENTS、docs、Obsidian、skill 和 ktWorkflow 文档入口。', inputSchema: { changedFiles: z.array(z.string()).default([]), project: z.string().default('root'), taskType: z.enum(taskTypeValues).optional(), }, title: 'KT Change Doc Sync', }, async (input) => response(await buildChangeDocSync(input)), ); server.registerTool( 'kt_commit_plan', { description: '按 KT 多仓库约定生成提交计划:按仓库分组、建议 commit message、提交命令和提交前检查。', inputSchema: { includePush: z.boolean().default(false), message: z.string().optional(), projects: z.array(z.string()).default([...defaultReviewProjects]), }, title: 'KT Commit Plan', }, async (input) => response(await buildCommitPlan(input)), ); server.registerTool( 'kt_push_plan', { description: '按 KT 多仓库约定生成推送计划:检查本地状态、待推提交、push 命令和远程异常提醒。', inputSchema: { branch: z.string().optional(), projects: z.array(z.string()).default([...defaultReviewProjects]), remote: z.string().default('origin'), }, title: 'KT Push Plan', }, async (input) => response(await buildPushPlan(input)), ); server.registerTool( 'kt_business_test_plan', { description: '生成 KT 固化业务链路测试计划:Admin 登录、博客 CRUD、QQBot 扫码/自动回复、Web/Playground 鉴权回跳。', inputSchema: { baseUrl: z.string().optional(), environment: z.enum(['local', 'remote']).default('local'), flow: z .enum([ 'admin-login', 'blog-crud', 'fflogs-command', 'qqbot-account-scan', 'qqbot-auto-reply', 'qqbot-login-sse', 'system-log-visualization', 'web-playground-auth', 'all', ]) .default('all'), }, title: 'KT Business Test Plan', }, async (input) => response(buildBusinessTestPlan(input)), ); server.registerTool( 'kt_napcat_upstream_audit', { description: '生成或执行 NapCatQQ 上游 release 审计:默认 dry-run,只读输出 hot zone、fork patch overlap、dry merge 和 Codex 审计入口;execute/useCodex 均需显式开启才会运行 Codex。', inputSchema: { artifactRoot: z .string() .default('.kt-workspace/test-artifacts/napcat-upstream-audit'), codexHome: z.string().optional(), createCandidateBranch: z.boolean().default(false), dryMergeConflict: z.boolean().default(false), execute: z.boolean().default(false), forkBranch: z.string().default(napcatAutomationDefaults.forkBranch), forkPatchFiles: z.array(z.string()).default([]), forkRepo: z.string().default(napcatAutomationDefaults.forkRepo), lastAcceptedUpstreamBase: z .string() .default(napcatAutomationDefaults.lastAcceptedUpstreamBase), outputSchemaPath: z.string().optional(), upstreamChangedFiles: z.array(z.string()).default([]), upstreamReleaseTag: z.string().default(napcatAutomationDefaults.upstreamReleaseRef), upstreamRepo: z.string().default(napcatAutomationDefaults.upstreamRepo), useCodex: z.boolean().default(false), workspaceRoot: z.string().default(napcatAutomationDefaults.workspaceRoot), }, title: 'KT NapCat Upstream Audit', }, async (input) => response(buildNapcatUpstreamAudit(input)), ); server.registerTool( 'kt_napcat_sync_candidate_review', { description: '生成 NapCatQQ sync candidate 人工/Codex 复审的 dry-run prompt、上下文和只读命令骨架;Task 5 不执行 Codex 或 Git 写操作。', inputSchema: { artifactRoot: z .string() .default('.kt-workspace/test-artifacts/napcat-automation/sync-candidate-review'), candidateBranch: z.string().default(napcatAutomationDefaults.candidateBranch), execute: z.boolean().default(false), forkBranch: z.string().default(napcatAutomationDefaults.forkBranch), forkRepo: z.string().default(napcatAutomationDefaults.forkRepo), lastAcceptedUpstreamBase: z .string() .default(napcatAutomationDefaults.lastAcceptedUpstreamBase), upstreamReleaseTag: z.string().default(napcatAutomationDefaults.upstreamReleaseRef), upstreamRepo: z.string().default(napcatAutomationDefaults.upstreamRepo), useCodex: z.boolean().default(false), workspaceRoot: z.string().default(napcatAutomationDefaults.workspaceRoot), }, title: 'KT NapCat Sync Candidate Review', }, async (input) => response(buildNapcatSyncCandidateReview(input)), ); server.registerTool( 'kt_napcat_runtime_release_readiness', { description: '生成 NapCat runtime release readiness dry-run prompt、上下文和只读检查骨架;不自动构建镜像、不调用 kubectl、不发布。', inputSchema: { apiImageTag: z.string().default(napcatAutomationDefaults.apiImageTag), artifactRoot: z .string() .default('.kt-workspace/test-artifacts/napcat-automation/runtime-release-readiness'), candidateBranch: z.string().default(napcatAutomationDefaults.candidateBranch), execute: z.boolean().default(false), forkBranch: z.string().default(napcatAutomationDefaults.forkBranch), forkRepo: z.string().default(napcatAutomationDefaults.forkRepo), napcatImageTag: z.string().default(napcatAutomationDefaults.runtimeImageTag), profile: z.string().default(napcatAutomationDefaults.runtimeProfile), upstreamReleaseTag: z.string().default(napcatAutomationDefaults.upstreamReleaseRef), upstreamRepo: z.string().default(napcatAutomationDefaults.upstreamRepo), useCodex: z.boolean().default(false), workspaceRoot: z.string().default(napcatAutomationDefaults.workspaceRoot), }, title: 'KT NapCat Runtime Release Readiness', }, async (input) => response(buildNapcatRuntimeReleaseReadiness(input)), ); server.registerTool( 'kt_napcat_remote_dev_handoff', { description: '生成 NapCat 自动化远程开发 handoff 的 dry-run prompt 和上下文骨架;不创建线程、不执行 Codex、不同步仓库。', inputSchema: { artifactRoot: z .string() .default('.kt-workspace/test-artifacts/napcat-automation/remote-dev-handoff'), candidateBranch: z.string().default(napcatAutomationDefaults.candidateBranch), execute: z.boolean().default(false), forkBranch: z.string().default(napcatAutomationDefaults.forkBranch), forkRepo: z.string().default(napcatAutomationDefaults.forkRepo), targetHost: z.string().default(napcatAutomationDefaults.targetHost), upstreamReleaseTag: z.string().default(napcatAutomationDefaults.upstreamReleaseRef), upstreamRepo: z.string().default(napcatAutomationDefaults.upstreamRepo), useCodex: z.boolean().default(false), workspaceRoot: z.string().default(napcatAutomationDefaults.workspaceRoot), }, title: 'KT NapCat Remote Dev Handoff', }, async (input) => response(buildNapcatRemoteDevHandoff(input)), ); server.registerTool( 'kt_nas_codex_bootstrap_plan', { description: '生成可信 NAS Codex 自动化环境 bootstrap 的 dry-run plan;Task 5 仅输出只读检查和占位计划,不安装、不同步、不写 systemd。', inputSchema: { artifactRoot: z .string() .default('.kt-workspace/test-artifacts/napcat-automation/nas-codex-bootstrap'), codexHome: z.string().default(napcatAutomationDefaults.codexHome), execute: z.boolean().default(false), serviceName: z.string().default(napcatAutomationDefaults.bootstrapServiceName), workspaceRoot: z.string().default(napcatAutomationDefaults.workspaceRoot), }, title: 'KT NAS Codex Bootstrap Plan', }, async (input) => response(buildNasCodexBootstrapPlan(input)), ); server.registerTool( 'kt_napcat_device_profile_check', { description: '静态检查 API NapCat 设备身份护栏:QQNT 可见 hostname、实体 OUI MAC、machine-info、runtime dir 和 DB_TIMEZONE。', inputSchema: { project: z.string().default('api'), }, title: 'KT NapCat Device Profile Check', }, async (input) => response(buildNapcatDeviceProfileCheck(input)), ); server.registerTool( 'kt_blocker_resolution', { description: '生成或写入卡点固化记录:问题点、稳定解法、后续入口、验证证据,并给出停止原样重试的下一步动作。', inputSchema: { attempts: z.number().int().min(0).max(20).default(1), command: z.string().optional(), dryRun: z.boolean().default(true), evidence: z.string().optional(), nextEntry: z.string().optional(), problem: z.string().optional(), project: z.string().default('root'), solution: z.string().optional(), title: z.string().optional(), }, title: 'KT Blocker Resolution', }, async (input) => response(buildBlockerResolution(input)), ); server.registerTool( 'kt_remote_health_check', { description: '生成或执行飞牛 NAS 远程只读健康检查:SSH、Docker、Gitea、Jenkins、MySQL、MinIO、Mosquitto、NapCat、k3d/K8s。', inputSchema: { execute: z.boolean().default(false), host: z.string().optional(), port: z.number().int().min(1).max(65_535).default(2202), services: z.array(z.string()).default([]), sshTarget: z.string().optional(), }, title: 'KT Remote Health Check', }, async (input) => response(await buildRemoteHealthCheck(input)), ); server.registerTool( 'kt_deploy_observation', { description: '生成或执行 API 发布后的只读部署观测:Jenkins、K8s Deployment、Pod、/health/runtime 和任务 smoke,并输出运行态证据。', inputSchema: { artifactRoot: z.string().optional(), buildNumber: z.string().optional(), container: z.string().default('api'), deployment: z.string().default('kt-template-online-api'), execute: z.boolean().default(false), expectedCommit: z.string().optional(), healthUrl: z.string().default('http://127.0.0.1:48085/health/runtime'), imageTag: z.string().optional(), jenkinsHome: z.string().default('/vol1/docker/jenkins/jenkins_home'), jobName: z.string().default('KT-Template/KT-Template-API/main'), kubeconfigPath: z .string() .default('/vol1/docker/kt-k8s/kubeconfig/kt-nas.jenkins.yaml'), namespace: z.string().default('kt-prod'), project: z.string().default('api'), selector: z.string().default('app=kt-template-online-api'), smoke: z.string().optional(), sshPort: z.number().int().min(1).max(65_535).optional(), sshTarget: z.string().default('nas'), }, title: 'KT Deploy Observation', }, async (input) => response(await buildDeployObservation(input)), ); server.registerTool( 'kt_db_sync_plan', { description: '生成 KT 数据库同步安全向导:源/目标确认、GTID 规避、目标备份、行数校验和通过后删除备份库。', inputSchema: { backupName: z.string().optional(), source: z.enum(['local', 'remote']).default('local'), target: z.enum(['local', 'remote']).default('remote'), }, title: 'KT DB Sync Plan', }, async (input) => response(buildDbSyncPlan(input)), ); server.registerTool( 'kt_component_workflow', { description: '按 KT 专项组件/链路输出防踩坑工作流:KtTable、BlogArgon、AdminAuth、QQBot、FF14Plugin、NapCatLogin、SystemLog、Knife4jSwagger、FnosK8s。', inputSchema: { target: z .enum([ 'AdminAuth', 'BlogArgon', 'FF14Plugin', 'FnosK8s', 'Knife4jSwagger', 'KtTable', 'NapCatLogin', 'QQBot', 'SystemLog', ]) .default('KtTable'), }, title: 'KT Component Workflow', }, async (input) => response(buildComponentWorkflow(input)), ); server.registerTool( 'kt_obsidian_context', { description: '读取 KT Obsidian 索引上下文:总入口、模块索引、文档矩阵、工作流关系,并可按 module/query 定向返回相关模块页摘要。', inputSchema: { includeCanvas: z.boolean().default(true), includeContent: z.boolean().default(true), maxDocuments: z.number().int().min(1).max(30).default(8), module: z.string().optional(), query: z.string().optional(), }, title: 'KT Obsidian Context', }, async (input) => response(readObsidianContext(input)), ); server.registerTool( 'kt_obsidian_validate', { description: '只读校验 KT Obsidian vault:JSON、Canvas、Base、Wiki 链接、Markdown 相对链接和旧引用。', inputSchema: { checkLayout: z.boolean().default(true), failOnWarnings: z.boolean().default(false), includeMarkdownLinks: z.boolean().default(true), includeStaleReferences: z.boolean().default(true), }, title: 'KT Obsidian Validate', }, async (input) => response(validateObsidianVault(input)), ); server.registerTool( 'kt_obsidian_sync', { description: '审计 KT Obsidian 工作流入口是否连通:必备文档、书签、核心插件、忽略目录和校验结果。', inputSchema: { checkLayout: z.boolean().default(true), failOnWarnings: z.boolean().default(false), includeMarkdownLinks: z.boolean().default(true), includeStaleReferences: z.boolean().default(true), includeValidation: z.boolean().default(true), }, title: 'KT Obsidian Sync', }, async (input) => response(syncObsidianWorkflow(input)), ); server.registerTool( 'kt_append_task_record', { description: '生成或写入 TASKS.md 改动记录。默认 dryRun=true,只预览不落盘。', inputSchema: { content: z.string().optional(), date: z.string().optional(), dryRun: z.boolean().default(true), scope: z.array(z.string()).default([]), testCase: z.string().optional(), title: z.string(), verification: z.string().optional(), }, title: 'Append KT Task Record', }, async (input) => response(appendTaskRecord(input)), ); server.registerTool( 'kt_commit_checklist', { description: '生成 KT 子仓库提交前检查清单,并校验 commit message 是否符合约定。', inputSchema: { message: z.string().optional(), project: z.string().default('root'), }, title: 'KT Commit Checklist', }, async (input) => response(await createCommitChecklist(input)), ); }