@vodailoc/kilo-kit-mcp 1.1.1 → 1.2.0
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.
- package/README.md +33 -3
- package/mcp/README.md +38 -10
- package/mcp/dist/formatters.js +142 -1
- package/mcp/dist/orchestration-audit.js +20 -0
- package/mcp/dist/orchestration-memory.js +258 -0
- package/mcp/dist/orchestration-types.js +1 -0
- package/mcp/dist/orchestrator.js +222 -0
- package/mcp/dist/question-templates.js +249 -0
- package/mcp/dist/route-analytics.js +149 -0
- package/mcp/dist/router.js +75 -82
- package/mcp/dist/routing-policy-data.js +241 -0
- package/mcp/dist/routing-policy.js +145 -0
- package/mcp/dist/server.js +93 -4
- package/mcp/dist/smoke-env.js +18 -0
- package/mcp/dist/smoke.js +68 -1
- package/mcp/package.json +1 -1
- package/package.json +1 -1
package/mcp/dist/server.js
CHANGED
|
@@ -1,27 +1,104 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
8
|
import { z } from "zod";
|
|
8
|
-
import { formatLoadedSkill, formatRoute, formatSkills, formatValidation, textResponse, } from "./formatters.js";
|
|
9
|
+
import { formatLoadedSkill, formatMemoryReport, formatOrchestration, formatRoute, formatRouteReport, formatSkills, formatValidation, textResponse, } from "./formatters.js";
|
|
10
|
+
import { createJsonlOrchestrationAudit, createNoopOrchestrationAudit } from "./orchestration-audit.js";
|
|
11
|
+
import { createSqliteOrchestrationMemory } from "./orchestration-memory.js";
|
|
12
|
+
import { createOrchestrator } from "./orchestrator.js";
|
|
9
13
|
import { resolveInsideRepo } from "./paths.js";
|
|
14
|
+
import { createInMemoryRouteAnalytics, createJsonlRouteAnalytics } from "./route-analytics.js";
|
|
10
15
|
import { createSkillRegistry } from "./registry.js";
|
|
11
16
|
import { routeIntent } from "./router.js";
|
|
12
17
|
import { validateSkills } from "./validator.js";
|
|
13
|
-
const SERVER_VERSION = "1.
|
|
18
|
+
const SERVER_VERSION = "1.2.0";
|
|
14
19
|
const DEFAULT_REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
15
20
|
const formatSchema = z.enum(["markdown", "json"]).default("markdown");
|
|
16
21
|
export async function createKiloKitServer(options = {}) {
|
|
17
22
|
const repoRoot = path.resolve(options.repoRoot ?? process.env.KILO_KIT_REPO_ROOT ?? DEFAULT_REPO_ROOT);
|
|
18
23
|
const registry = await createSkillRegistry({ repoRoot });
|
|
24
|
+
const routeAnalytics = process.env.KILO_KIT_WRITE_DECISIONS === "true"
|
|
25
|
+
? createJsonlRouteAnalytics({
|
|
26
|
+
filePath: process.env.KILO_KIT_DECISION_TRAIL_PATH
|
|
27
|
+
? path.resolve(process.env.KILO_KIT_DECISION_TRAIL_PATH)
|
|
28
|
+
: resolveInsideRepo(repoRoot, ".kilo/decision-trail.jsonl"),
|
|
29
|
+
})
|
|
30
|
+
: createInMemoryRouteAnalytics();
|
|
31
|
+
const orchestrationMemory = await createSqliteOrchestrationMemory({
|
|
32
|
+
filePath: path.resolve(process.env.KILO_KIT_MEMORY_PATH ?? path.join(os.homedir(), ".kilo-kit/orchestrator.sqlite")),
|
|
33
|
+
});
|
|
34
|
+
const orchestrationAudit = process.env.KILO_KIT_ORCHESTRATION_AUDIT_PATH
|
|
35
|
+
? createJsonlOrchestrationAudit(path.resolve(process.env.KILO_KIT_ORCHESTRATION_AUDIT_PATH))
|
|
36
|
+
: createNoopOrchestrationAudit();
|
|
37
|
+
const orchestrator = createOrchestrator({
|
|
38
|
+
registry,
|
|
39
|
+
memory: orchestrationMemory,
|
|
40
|
+
audit: orchestrationAudit,
|
|
41
|
+
});
|
|
19
42
|
const server = new McpServer({
|
|
20
43
|
name: "kilo-kit",
|
|
21
44
|
version: SERVER_VERSION,
|
|
22
45
|
}, {
|
|
23
|
-
instructions: "Use kilo_route_intent before selecting a Kilo-Kit workflow skill. Load one selected skill with kilo_get_skill, then follow its instructions.
|
|
46
|
+
instructions: "Use kilo_route_intent before selecting a Kilo-Kit workflow skill. Load one selected skill with kilo_get_skill, then follow its instructions. Route telemetry is in-memory by default and only persists when KILO_KIT_WRITE_DECISIONS=true.",
|
|
24
47
|
});
|
|
48
|
+
server.registerTool("kilo_orchestrate_task", {
|
|
49
|
+
title: "Kilo-Kit C4 Orchestrate Task",
|
|
50
|
+
description: "Central C4 orchestration gate. Routes internally, enforces brainstorming-first, asks required questions, checks memory suggestions, and releases a final workflow only after confirmation.",
|
|
51
|
+
inputSchema: {
|
|
52
|
+
message: z.string().min(1).max(4000).describe("Current user request or task summary."),
|
|
53
|
+
context: z
|
|
54
|
+
.object({
|
|
55
|
+
files: z.array(z.string().max(300)).max(30).optional(),
|
|
56
|
+
mode: z.string().max(80).optional(),
|
|
57
|
+
previousErrors: z.string().max(2000).optional(),
|
|
58
|
+
projectFingerprint: z.string().max(200).optional(),
|
|
59
|
+
})
|
|
60
|
+
.optional(),
|
|
61
|
+
sessionId: z.string().min(1).max(120).optional(),
|
|
62
|
+
answers: z.record(z.string().max(2000)).optional(),
|
|
63
|
+
memoryConfirmations: z.record(z.enum(["accepted", "rejected"])).optional(),
|
|
64
|
+
format: formatSchema.optional(),
|
|
65
|
+
},
|
|
66
|
+
annotations: {
|
|
67
|
+
readOnlyHint: true,
|
|
68
|
+
destructiveHint: false,
|
|
69
|
+
idempotentHint: false,
|
|
70
|
+
},
|
|
71
|
+
}, async ({ message, context, sessionId, answers, memoryConfirmations, format }) => {
|
|
72
|
+
const result = orchestrator.orchestrate({
|
|
73
|
+
message,
|
|
74
|
+
...(context
|
|
75
|
+
? {
|
|
76
|
+
context: {
|
|
77
|
+
...(context.files ? { files: context.files } : {}),
|
|
78
|
+
...(context.mode ? { mode: context.mode } : {}),
|
|
79
|
+
...(context.previousErrors ? { previousErrors: context.previousErrors } : {}),
|
|
80
|
+
...(context.projectFingerprint ? { projectFingerprint: context.projectFingerprint } : {}),
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
: {}),
|
|
84
|
+
...(sessionId ? { sessionId } : {}),
|
|
85
|
+
...(answers ? { answers } : {}),
|
|
86
|
+
...(memoryConfirmations ? { memoryConfirmations } : {}),
|
|
87
|
+
});
|
|
88
|
+
return textResponse(formatOrchestration(result, normalizeFormat(format)));
|
|
89
|
+
});
|
|
90
|
+
server.registerTool("kilo_memory_report", {
|
|
91
|
+
title: "Kilo-Kit C4 Memory Report",
|
|
92
|
+
description: "Read global C4 memory facts, decisions, and recent suggestions.",
|
|
93
|
+
inputSchema: {
|
|
94
|
+
format: formatSchema.optional(),
|
|
95
|
+
},
|
|
96
|
+
annotations: {
|
|
97
|
+
readOnlyHint: true,
|
|
98
|
+
destructiveHint: false,
|
|
99
|
+
idempotentHint: true,
|
|
100
|
+
},
|
|
101
|
+
}, async ({ format }) => textResponse(formatMemoryReport(orchestrationMemory.report(), normalizeFormat(format))));
|
|
25
102
|
server.registerTool("kilo_search_skills", {
|
|
26
103
|
title: "Search Kilo-Kit Skills",
|
|
27
104
|
description: "Search the Kilo-Kit skill library by natural-language query. Use this for broad discovery before loading a specific skill.",
|
|
@@ -99,9 +176,21 @@ export async function createKiloKitServer(options = {}) {
|
|
|
99
176
|
message,
|
|
100
177
|
...(routeContext ? { context: routeContext } : {}),
|
|
101
178
|
...(limit ? { limit } : {}),
|
|
102
|
-
});
|
|
179
|
+
}, { analytics: routeAnalytics });
|
|
103
180
|
return textResponse(formatRoute(result, normalizeFormat(format)));
|
|
104
181
|
});
|
|
182
|
+
server.registerTool("kilo_route_report", {
|
|
183
|
+
title: "Kilo-Kit Route Report",
|
|
184
|
+
description: "Summarize route telemetry: top skills, task modes, workflow chains, score averages, and conflict penalties.",
|
|
185
|
+
inputSchema: {
|
|
186
|
+
format: formatSchema.optional(),
|
|
187
|
+
},
|
|
188
|
+
annotations: {
|
|
189
|
+
readOnlyHint: true,
|
|
190
|
+
destructiveHint: false,
|
|
191
|
+
idempotentHint: true,
|
|
192
|
+
},
|
|
193
|
+
}, async ({ format }) => textResponse(formatRouteReport(routeAnalytics.report(), normalizeFormat(format))));
|
|
105
194
|
server.registerTool("kilo_validate_skills", {
|
|
106
195
|
title: "Validate Kilo-Kit Skills",
|
|
107
196
|
description: "Run the Kilo-Kit skill validator and return a concise quality-gate summary. This is read-only and does not modify files.",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getDefaultEnvironment } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
2
|
+
const FORWARDED_ENV_VARS = [
|
|
3
|
+
"KILO_KIT_WRITE_DECISIONS",
|
|
4
|
+
"KILO_KIT_DECISION_TRAIL_PATH",
|
|
5
|
+
"KILO_KIT_REPO_ROOT",
|
|
6
|
+
"KILO_KIT_MEMORY_PATH",
|
|
7
|
+
"KILO_KIT_ORCHESTRATION_AUDIT_PATH",
|
|
8
|
+
];
|
|
9
|
+
export function buildSmokeEnvironment(sourceEnv = process.env, baseEnv = getDefaultEnvironment()) {
|
|
10
|
+
const env = { ...baseEnv };
|
|
11
|
+
for (const name of FORWARDED_ENV_VARS) {
|
|
12
|
+
const value = sourceEnv[name];
|
|
13
|
+
if (value !== undefined) {
|
|
14
|
+
env[name] = value;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return env;
|
|
18
|
+
}
|
package/mcp/dist/smoke.js
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
2
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
+
import { buildSmokeEnvironment } from "./smoke-env.js";
|
|
3
4
|
const transport = new StdioClientTransport({
|
|
4
5
|
command: process.env.KILO_KIT_SMOKE_COMMAND ?? process.execPath,
|
|
5
6
|
args: parseSmokeArgs(),
|
|
7
|
+
env: buildSmokeEnvironment(),
|
|
6
8
|
});
|
|
7
9
|
const client = new Client({ name: "kilo-kit-smoke", version: "1.0.0" });
|
|
8
10
|
try {
|
|
9
11
|
await client.connect(transport);
|
|
10
12
|
const tools = await client.listTools();
|
|
11
13
|
const toolNames = tools.tools.map((tool) => tool.name);
|
|
12
|
-
for (const required of [
|
|
14
|
+
for (const required of [
|
|
15
|
+
"kilo_route_intent",
|
|
16
|
+
"kilo_get_skill",
|
|
17
|
+
"kilo_validate_skills",
|
|
18
|
+
"kilo_route_report",
|
|
19
|
+
"kilo_orchestrate_task",
|
|
20
|
+
"kilo_memory_report",
|
|
21
|
+
]) {
|
|
13
22
|
if (!toolNames.includes(required)) {
|
|
14
23
|
throw new Error(`Missing expected tool: ${required}`);
|
|
15
24
|
}
|
|
@@ -26,6 +35,64 @@ try {
|
|
|
26
35
|
if (!routeText.includes("engineering/tdd")) {
|
|
27
36
|
throw new Error(`Smoke route did not recommend engineering/tdd: ${routeText}`);
|
|
28
37
|
}
|
|
38
|
+
const report = await client.callTool({
|
|
39
|
+
name: "kilo_route_report",
|
|
40
|
+
arguments: { format: "json" },
|
|
41
|
+
});
|
|
42
|
+
const reportText = extractFirstText(report);
|
|
43
|
+
if (!reportText.includes('"totalEvents": 1') || !reportText.includes("engineering/tdd")) {
|
|
44
|
+
throw new Error(`Smoke route report did not include the routed event: ${reportText}`);
|
|
45
|
+
}
|
|
46
|
+
const orchestration = await client.callTool({
|
|
47
|
+
name: "kilo_orchestrate_task",
|
|
48
|
+
arguments: {
|
|
49
|
+
message: "Fix bug login, viết test trước",
|
|
50
|
+
context: { files: ["src/auth/login.ts"], mode: "coding", projectFingerprint: "smoke:typescript" },
|
|
51
|
+
format: "json",
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
const orchestrationText = extractFirstText(orchestration);
|
|
55
|
+
const orchestrationResult = JSON.parse(orchestrationText);
|
|
56
|
+
if (orchestrationResult.state !== "brainstorming_required") {
|
|
57
|
+
throw new Error(`Smoke orchestration did not require brainstorming: ${orchestrationText}`);
|
|
58
|
+
}
|
|
59
|
+
if (orchestrationResult.workflow?.[0]?.skill?.id !== "productivity/brainstorming") {
|
|
60
|
+
throw new Error(`Smoke orchestration did not start with brainstorming: ${orchestrationText}`);
|
|
61
|
+
}
|
|
62
|
+
if (!orchestrationResult.questions?.some((question) => question.id === "test_command" || question.skillId === "engineering/tdd")) {
|
|
63
|
+
throw new Error(`Smoke orchestration did not include TDD questions: ${orchestrationText}`);
|
|
64
|
+
}
|
|
65
|
+
const readyOrchestration = await client.callTool({
|
|
66
|
+
name: "kilo_orchestrate_task",
|
|
67
|
+
arguments: {
|
|
68
|
+
message: "Fix bug login, viết test trước",
|
|
69
|
+
sessionId: orchestrationResult.sessionId,
|
|
70
|
+
answers: {
|
|
71
|
+
goal: "Fix login failure",
|
|
72
|
+
scope: "src/auth/login.ts",
|
|
73
|
+
success_criteria: "login test passes",
|
|
74
|
+
failing_behavior: "valid credentials are rejected",
|
|
75
|
+
test_command: "npm test -- login",
|
|
76
|
+
},
|
|
77
|
+
format: "json",
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
const readyText = extractFirstText(readyOrchestration);
|
|
81
|
+
const readyResult = JSON.parse(readyText);
|
|
82
|
+
if (readyResult.state !== "ready" || readyResult.firstSkillToLoad?.id !== "productivity/brainstorming") {
|
|
83
|
+
throw new Error(`Smoke orchestration did not release final workflow after answers: ${readyText}`);
|
|
84
|
+
}
|
|
85
|
+
if (readyResult.finalWorkflow?.[0]?.skill?.id !== "productivity/brainstorming") {
|
|
86
|
+
throw new Error(`Smoke final workflow did not start with brainstorming: ${readyText}`);
|
|
87
|
+
}
|
|
88
|
+
const memoryReport = await client.callTool({
|
|
89
|
+
name: "kilo_memory_report",
|
|
90
|
+
arguments: { format: "json" },
|
|
91
|
+
});
|
|
92
|
+
const memoryReportText = extractFirstText(memoryReport);
|
|
93
|
+
if (!memoryReportText.includes('"facts"')) {
|
|
94
|
+
throw new Error(`Smoke memory report did not return memory facts: ${memoryReportText}`);
|
|
95
|
+
}
|
|
29
96
|
const skill = await client.callTool({
|
|
30
97
|
name: "kilo_get_skill",
|
|
31
98
|
arguments: { category: "engineering", skill: "tdd", maxChars: 800 },
|
package/mcp/package.json
CHANGED