@kkelly-offical/kkcode 0.1.2
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/LICENSE +674 -0
- package/README.md +445 -0
- package/package.json +46 -0
- package/src/agent/agent.mjs +170 -0
- package/src/agent/custom-agent-loader.mjs +158 -0
- package/src/agent/generator.mjs +115 -0
- package/src/agent/prompt/architect.txt +36 -0
- package/src/agent/prompt/build-fixer.txt +71 -0
- package/src/agent/prompt/build.txt +101 -0
- package/src/agent/prompt/compaction.txt +12 -0
- package/src/agent/prompt/explore.txt +29 -0
- package/src/agent/prompt/guide.txt +40 -0
- package/src/agent/prompt/longagent.txt +178 -0
- package/src/agent/prompt/plan.txt +50 -0
- package/src/agent/prompt/researcher.txt +23 -0
- package/src/agent/prompt/reviewer.txt +44 -0
- package/src/agent/prompt/security-reviewer.txt +62 -0
- package/src/agent/prompt/tdd-guide.txt +84 -0
- package/src/agent/prompt/title.txt +8 -0
- package/src/command/custom-commands.mjs +57 -0
- package/src/commands/agent.mjs +71 -0
- package/src/commands/audit.mjs +77 -0
- package/src/commands/background.mjs +86 -0
- package/src/commands/chat.mjs +114 -0
- package/src/commands/command.mjs +41 -0
- package/src/commands/config.mjs +44 -0
- package/src/commands/doctor.mjs +148 -0
- package/src/commands/hook.mjs +29 -0
- package/src/commands/init.mjs +141 -0
- package/src/commands/longagent.mjs +100 -0
- package/src/commands/mcp.mjs +89 -0
- package/src/commands/permission.mjs +36 -0
- package/src/commands/prompt.mjs +42 -0
- package/src/commands/review.mjs +266 -0
- package/src/commands/rule.mjs +34 -0
- package/src/commands/session.mjs +235 -0
- package/src/commands/theme.mjs +98 -0
- package/src/commands/usage.mjs +91 -0
- package/src/config/defaults.mjs +195 -0
- package/src/config/import-config.mjs +76 -0
- package/src/config/load-config.mjs +76 -0
- package/src/config/schema.mjs +509 -0
- package/src/context.mjs +40 -0
- package/src/core/constants.mjs +46 -0
- package/src/core/errors.mjs +57 -0
- package/src/core/events.mjs +29 -0
- package/src/core/types.mjs +57 -0
- package/src/github/api.mjs +78 -0
- package/src/github/auth.mjs +286 -0
- package/src/github/flow.mjs +298 -0
- package/src/github/workspace.mjs +212 -0
- package/src/index.mjs +82 -0
- package/src/knowledge/api-design.txt +9 -0
- package/src/knowledge/cpp.txt +10 -0
- package/src/knowledge/docker.txt +10 -0
- package/src/knowledge/dotnet.txt +9 -0
- package/src/knowledge/electron.txt +10 -0
- package/src/knowledge/flutter.txt +10 -0
- package/src/knowledge/go.txt +9 -0
- package/src/knowledge/graphql.txt +10 -0
- package/src/knowledge/java.txt +9 -0
- package/src/knowledge/kotlin.txt +10 -0
- package/src/knowledge/loader.mjs +125 -0
- package/src/knowledge/next.txt +8 -0
- package/src/knowledge/node.txt +8 -0
- package/src/knowledge/nuxt.txt +9 -0
- package/src/knowledge/php.txt +10 -0
- package/src/knowledge/python.txt +10 -0
- package/src/knowledge/react-native.txt +10 -0
- package/src/knowledge/react.txt +9 -0
- package/src/knowledge/ruby.txt +11 -0
- package/src/knowledge/rust.txt +9 -0
- package/src/knowledge/svelte.txt +9 -0
- package/src/knowledge/swift.txt +10 -0
- package/src/knowledge/tailwind.txt +10 -0
- package/src/knowledge/testing.txt +8 -0
- package/src/knowledge/typescript.txt +8 -0
- package/src/knowledge/vue.txt +9 -0
- package/src/mcp/client-http.mjs +157 -0
- package/src/mcp/client-sse.mjs +286 -0
- package/src/mcp/client-stdio.mjs +451 -0
- package/src/mcp/registry.mjs +394 -0
- package/src/mcp/stdio-framing.mjs +127 -0
- package/src/orchestration/background-manager.mjs +358 -0
- package/src/orchestration/background-worker.mjs +245 -0
- package/src/orchestration/longagent-manager.mjs +116 -0
- package/src/orchestration/stage-scheduler.mjs +489 -0
- package/src/orchestration/subagent-router.mjs +62 -0
- package/src/orchestration/task-scheduler.mjs +74 -0
- package/src/permission/engine.mjs +92 -0
- package/src/permission/exec-policy.mjs +372 -0
- package/src/permission/prompt.mjs +39 -0
- package/src/permission/rules.mjs +120 -0
- package/src/permission/workspace-trust.mjs +44 -0
- package/src/plugin/builtin-hooks/console-warn.mjs +41 -0
- package/src/plugin/builtin-hooks/extract-patterns.mjs +75 -0
- package/src/plugin/builtin-hooks/post-edit-format.mjs +57 -0
- package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +61 -0
- package/src/plugin/builtin-hooks/strategic-compaction.mjs +38 -0
- package/src/plugin/hook-bus.mjs +154 -0
- package/src/provider/anthropic.mjs +389 -0
- package/src/provider/ollama.mjs +236 -0
- package/src/provider/openai-compatible.mjs +1 -0
- package/src/provider/openai.mjs +339 -0
- package/src/provider/retry-policy.mjs +68 -0
- package/src/provider/router.mjs +228 -0
- package/src/provider/sse.mjs +91 -0
- package/src/repl.mjs +2929 -0
- package/src/review/diff-parser.mjs +36 -0
- package/src/review/rejection-queue.mjs +62 -0
- package/src/review/review-store.mjs +21 -0
- package/src/review/risk-score.mjs +61 -0
- package/src/rules/load-rules.mjs +64 -0
- package/src/runtime.mjs +1 -0
- package/src/session/checkpoint.mjs +239 -0
- package/src/session/compaction.mjs +276 -0
- package/src/session/engine.mjs +225 -0
- package/src/session/instinct-manager.mjs +172 -0
- package/src/session/instruction-loader.mjs +25 -0
- package/src/session/longagent-plan.mjs +329 -0
- package/src/session/longagent-scaffold.mjs +100 -0
- package/src/session/longagent.mjs +1462 -0
- package/src/session/loop.mjs +905 -0
- package/src/session/memory-loader.mjs +75 -0
- package/src/session/project-context.mjs +367 -0
- package/src/session/prompt/anthropic.txt +151 -0
- package/src/session/prompt/beast.txt +37 -0
- package/src/session/prompt/max-steps.txt +6 -0
- package/src/session/prompt/plan.txt +9 -0
- package/src/session/prompt/qwen.txt +46 -0
- package/src/session/prompt-loader.mjs +18 -0
- package/src/session/recovery.mjs +52 -0
- package/src/session/store.mjs +503 -0
- package/src/session/system-prompt.mjs +260 -0
- package/src/session/task-validator.mjs +266 -0
- package/src/session/usability-gates.mjs +379 -0
- package/src/skill/builtin/backend-patterns.mjs +123 -0
- package/src/skill/builtin/commit.mjs +64 -0
- package/src/skill/builtin/debug.mjs +45 -0
- package/src/skill/builtin/frontend-patterns.mjs +120 -0
- package/src/skill/builtin/frontend.mjs +188 -0
- package/src/skill/builtin/init.mjs +220 -0
- package/src/skill/builtin/review.mjs +49 -0
- package/src/skill/builtin/security-checklist.mjs +80 -0
- package/src/skill/builtin/tdd.mjs +54 -0
- package/src/skill/generator.mjs +113 -0
- package/src/skill/registry.mjs +336 -0
- package/src/storage/audit-store.mjs +83 -0
- package/src/storage/event-log.mjs +82 -0
- package/src/storage/ghost-commit-store.mjs +235 -0
- package/src/storage/json-store.mjs +53 -0
- package/src/storage/paths.mjs +148 -0
- package/src/theme/color.mjs +64 -0
- package/src/theme/default-theme.mjs +29 -0
- package/src/theme/load-theme.mjs +71 -0
- package/src/theme/markdown.mjs +135 -0
- package/src/theme/schema.mjs +45 -0
- package/src/theme/status-bar.mjs +158 -0
- package/src/tool/audit-wrapper.mjs +38 -0
- package/src/tool/edit-transaction.mjs +126 -0
- package/src/tool/executor.mjs +109 -0
- package/src/tool/file-lock-manager.mjs +85 -0
- package/src/tool/git-auto.mjs +545 -0
- package/src/tool/git-full-auto.mjs +478 -0
- package/src/tool/image-util.mjs +276 -0
- package/src/tool/prompt/background_cancel.txt +1 -0
- package/src/tool/prompt/background_output.txt +1 -0
- package/src/tool/prompt/bash.txt +71 -0
- package/src/tool/prompt/codesearch.txt +18 -0
- package/src/tool/prompt/edit.txt +27 -0
- package/src/tool/prompt/enter_plan.txt +74 -0
- package/src/tool/prompt/exit_plan.txt +62 -0
- package/src/tool/prompt/glob.txt +33 -0
- package/src/tool/prompt/grep.txt +43 -0
- package/src/tool/prompt/list.txt +8 -0
- package/src/tool/prompt/multiedit.txt +20 -0
- package/src/tool/prompt/notebookedit.txt +21 -0
- package/src/tool/prompt/patch.txt +24 -0
- package/src/tool/prompt/question.txt +44 -0
- package/src/tool/prompt/read.txt +40 -0
- package/src/tool/prompt/task.txt +83 -0
- package/src/tool/prompt/todowrite.txt +117 -0
- package/src/tool/prompt/webfetch.txt +38 -0
- package/src/tool/prompt/websearch.txt +43 -0
- package/src/tool/prompt/write.txt +38 -0
- package/src/tool/prompt-loader.mjs +18 -0
- package/src/tool/question-prompt.mjs +86 -0
- package/src/tool/registry.mjs +1309 -0
- package/src/tool/task-tool.mjs +28 -0
- package/src/ui/activity-renderer.mjs +410 -0
- package/src/ui/repl-dashboard.mjs +357 -0
- package/src/usage/pricing.mjs +121 -0
- package/src/usage/usage-meter.mjs +113 -0
- package/src/util/git.mjs +496 -0
- package/src/util/template.mjs +10 -0
- package/src/util/yaml.mjs +100 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import { spawn } from "node:child_process"
|
|
2
|
+
import { McpError } from "../core/errors.mjs"
|
|
3
|
+
import { EventBus } from "../core/events.mjs"
|
|
4
|
+
import { EVENT_TYPES } from "../core/constants.mjs"
|
|
5
|
+
import { createStdioFramingDecoder, encodeRpcMessage } from "./stdio-framing.mjs"
|
|
6
|
+
|
|
7
|
+
const VALID_FRAMING = new Set(["auto", "content-length", "newline"])
|
|
8
|
+
const VALID_HEALTH_METHOD = new Set(["auto", "ping", "tools_list"])
|
|
9
|
+
|
|
10
|
+
function normalizeFraming(value) {
|
|
11
|
+
const framing = String(value || "auto").toLowerCase()
|
|
12
|
+
return VALID_FRAMING.has(framing) ? framing : "auto"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeHealthMethod(value) {
|
|
16
|
+
const method = String(value || "auto").toLowerCase()
|
|
17
|
+
return VALID_HEALTH_METHOD.has(method) ? method : "auto"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function classifySpawnError(error) {
|
|
21
|
+
const code = String(error?.code || "").toUpperCase()
|
|
22
|
+
const msg = String(error?.message || error || "")
|
|
23
|
+
if (code === "ENOENT" || code === "EACCES") return "spawn_failed"
|
|
24
|
+
if (msg.includes("ENOENT") || msg.includes("EACCES") || msg.includes("spawn")) return "spawn_failed"
|
|
25
|
+
return "unknown"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeToolResult(result, serverName, toolName) {
|
|
29
|
+
if (result?.isError) {
|
|
30
|
+
const text = Array.isArray(result.content)
|
|
31
|
+
? result.content.map((item) => item?.text || "").join("\n").trim()
|
|
32
|
+
: ""
|
|
33
|
+
throw new McpError(text || "mcp tool returned isError", {
|
|
34
|
+
reason: "bad_response",
|
|
35
|
+
server: serverName,
|
|
36
|
+
action: `tools/call:${toolName}`,
|
|
37
|
+
phase: "request"
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const content = Array.isArray(result?.content) ? result.content : null
|
|
42
|
+
const contentText = content
|
|
43
|
+
? content.map((item) => (typeof item?.text === "string" ? item.text : "")).join("\n").trim()
|
|
44
|
+
: ""
|
|
45
|
+
const output =
|
|
46
|
+
contentText ||
|
|
47
|
+
(typeof result?.output === "string" ? result.output : "") ||
|
|
48
|
+
(typeof result === "string" ? result : JSON.stringify(result))
|
|
49
|
+
|
|
50
|
+
return content
|
|
51
|
+
? { output, raw: result, content }
|
|
52
|
+
: { output, raw: result }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createStdioMcpClient(serverName, config = {}) {
|
|
56
|
+
const command = config.command
|
|
57
|
+
const cmdArgs = Array.isArray(config.args) ? config.args : []
|
|
58
|
+
const envOverrides = config.env || {}
|
|
59
|
+
const startupTimeoutMs = Math.max(100, Number(config.startup_timeout_ms || 5000))
|
|
60
|
+
const requestTimeoutMs = Math.max(100, Number(config.request_timeout_ms || config.timeout_ms || 30000))
|
|
61
|
+
const healthCheckMethod = normalizeHealthMethod(config.health_check_method)
|
|
62
|
+
const configuredFraming = normalizeFraming(config.framing)
|
|
63
|
+
const isWindows = process.platform === "win32"
|
|
64
|
+
const explicitShell = config.shell === true || (config.shell !== false && isWindows)
|
|
65
|
+
|
|
66
|
+
let executable
|
|
67
|
+
let spawnArgs
|
|
68
|
+
if (Array.isArray(command)) {
|
|
69
|
+
executable = command[0]
|
|
70
|
+
spawnArgs = command.slice(1)
|
|
71
|
+
} else {
|
|
72
|
+
executable = command
|
|
73
|
+
spawnArgs = cmdArgs
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!executable) {
|
|
77
|
+
throw new McpError(`mcp server "${serverName}" missing command`, {
|
|
78
|
+
reason: "spawn_failed",
|
|
79
|
+
server: serverName,
|
|
80
|
+
phase: "startup"
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let child = null
|
|
85
|
+
let lifecycle = "closed"
|
|
86
|
+
let nextId = 1
|
|
87
|
+
let initialized = false
|
|
88
|
+
let activeFraming = configuredFraming === "auto" ? "content-length" : configuredFraming
|
|
89
|
+
let decoder = createStdioFramingDecoder({
|
|
90
|
+
framing: configuredFraming === "auto" ? "auto" : activeFraming
|
|
91
|
+
})
|
|
92
|
+
let malformedSeen = false
|
|
93
|
+
let malformedSnippet = ""
|
|
94
|
+
let stderrLines = []
|
|
95
|
+
let ignoreClose = false
|
|
96
|
+
|
|
97
|
+
const pending = new Map()
|
|
98
|
+
|
|
99
|
+
function resetRuntime() {
|
|
100
|
+
decoder = createStdioFramingDecoder({
|
|
101
|
+
framing: configuredFraming === "auto" ? "auto" : activeFraming
|
|
102
|
+
})
|
|
103
|
+
malformedSeen = false
|
|
104
|
+
malformedSnippet = ""
|
|
105
|
+
stderrLines = []
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function appendStderr(chunk) {
|
|
109
|
+
const text = String(chunk || "").trim()
|
|
110
|
+
if (!text) return
|
|
111
|
+
stderrLines.push(text)
|
|
112
|
+
if (stderrLines.length > 8) stderrLines = stderrLines.slice(stderrLines.length - 8)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function rejectPending(reason, message, details = {}) {
|
|
116
|
+
for (const [, entry] of pending) {
|
|
117
|
+
clearTimeout(entry.timer)
|
|
118
|
+
entry.reject(
|
|
119
|
+
new McpError(message, {
|
|
120
|
+
reason,
|
|
121
|
+
server: serverName,
|
|
122
|
+
action: entry.method,
|
|
123
|
+
phase: entry.phase || details.phase || "request",
|
|
124
|
+
stderrSnippet: stderrLines.join(" | ") || undefined,
|
|
125
|
+
...details
|
|
126
|
+
})
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
pending.clear()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function cleanupChild() {
|
|
133
|
+
child = null
|
|
134
|
+
initialized = false
|
|
135
|
+
lifecycle = "closed"
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function startProcess() {
|
|
139
|
+
if (child && lifecycle !== "closed") return
|
|
140
|
+
|
|
141
|
+
resetRuntime()
|
|
142
|
+
lifecycle = "starting"
|
|
143
|
+
ignoreClose = false
|
|
144
|
+
|
|
145
|
+
await new Promise((resolve, reject) => {
|
|
146
|
+
let settled = false
|
|
147
|
+
const proc = spawn(executable, spawnArgs, {
|
|
148
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
149
|
+
env: { ...process.env, ...envOverrides },
|
|
150
|
+
windowsHide: true,
|
|
151
|
+
shell: explicitShell
|
|
152
|
+
})
|
|
153
|
+
child = proc
|
|
154
|
+
|
|
155
|
+
const timer = setTimeout(() => {
|
|
156
|
+
if (settled) return
|
|
157
|
+
settled = true
|
|
158
|
+
try { proc.kill() } catch {}
|
|
159
|
+
reject(
|
|
160
|
+
new McpError(`mcp server "${serverName}" startup timeout after ${startupTimeoutMs}ms`, {
|
|
161
|
+
reason: "timeout",
|
|
162
|
+
server: serverName,
|
|
163
|
+
phase: "startup"
|
|
164
|
+
})
|
|
165
|
+
)
|
|
166
|
+
}, startupTimeoutMs)
|
|
167
|
+
|
|
168
|
+
proc.once("spawn", () => {
|
|
169
|
+
if (settled) return
|
|
170
|
+
settled = true
|
|
171
|
+
clearTimeout(timer)
|
|
172
|
+
lifecycle = "running"
|
|
173
|
+
resolve()
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
proc.once("error", (err) => {
|
|
177
|
+
const reason = classifySpawnError(err)
|
|
178
|
+
if (!settled) {
|
|
179
|
+
settled = true
|
|
180
|
+
clearTimeout(timer)
|
|
181
|
+
reject(
|
|
182
|
+
new McpError(`mcp server "${serverName}" process error: ${err.message}`, {
|
|
183
|
+
reason,
|
|
184
|
+
server: serverName,
|
|
185
|
+
phase: "startup"
|
|
186
|
+
})
|
|
187
|
+
)
|
|
188
|
+
} else {
|
|
189
|
+
rejectPending(reason, `mcp server "${serverName}" process error: ${err.message}`, { phase: "request" })
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
proc.stdout.on("data", (chunk) => {
|
|
194
|
+
let payloads = []
|
|
195
|
+
try {
|
|
196
|
+
payloads = decoder.push(chunk)
|
|
197
|
+
} catch (error) {
|
|
198
|
+
malformedSeen = true
|
|
199
|
+
malformedSnippet = String(error.message || "invalid framing").slice(0, 240)
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
for (const payload of payloads) {
|
|
204
|
+
let msg
|
|
205
|
+
try {
|
|
206
|
+
msg = JSON.parse(payload)
|
|
207
|
+
} catch {
|
|
208
|
+
malformedSeen = true
|
|
209
|
+
malformedSnippet = String(payload || "").slice(0, 240)
|
|
210
|
+
continue
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (msg?.id != null && pending.has(msg.id)) {
|
|
214
|
+
const entry = pending.get(msg.id)
|
|
215
|
+
pending.delete(msg.id)
|
|
216
|
+
clearTimeout(entry.timer)
|
|
217
|
+
if (msg.error) {
|
|
218
|
+
entry.reject(
|
|
219
|
+
new McpError(
|
|
220
|
+
`mcp server "${serverName}" error: ${msg.error.message || JSON.stringify(msg.error)}`,
|
|
221
|
+
{
|
|
222
|
+
reason: "bad_response",
|
|
223
|
+
server: serverName,
|
|
224
|
+
action: entry.method,
|
|
225
|
+
phase: entry.phase,
|
|
226
|
+
code: msg.error.code,
|
|
227
|
+
stderrSnippet: stderrLines.join(" | ") || undefined
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
} else {
|
|
232
|
+
const elapsed = Date.now() - entry.startedAt
|
|
233
|
+
EventBus.emit({
|
|
234
|
+
type: EVENT_TYPES.MCP_REQUEST,
|
|
235
|
+
payload: { server: serverName, action: entry.method, elapsed, transport: "stdio" }
|
|
236
|
+
}).catch(() => {})
|
|
237
|
+
entry.resolve(msg.result ?? {})
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
proc.stderr.on("data", (chunk) => appendStderr(chunk))
|
|
244
|
+
|
|
245
|
+
proc.on("close", (code, signal) => {
|
|
246
|
+
if (ignoreClose) {
|
|
247
|
+
cleanupChild()
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
const reason = malformedSeen ? "bad_response" : "server_crash"
|
|
251
|
+
const extra = malformedSeen && malformedSnippet
|
|
252
|
+
? `; malformed stdout: ${malformedSnippet}`
|
|
253
|
+
: ""
|
|
254
|
+
rejectPending(
|
|
255
|
+
reason,
|
|
256
|
+
`mcp server "${serverName}" process exited unexpectedly (code=${code ?? "null"}, signal=${signal || "null"})${extra}`,
|
|
257
|
+
{ phase: lifecycle === "starting" ? "startup" : "request" }
|
|
258
|
+
)
|
|
259
|
+
cleanupChild()
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function shutdownProcess() {
|
|
265
|
+
if (!child) return
|
|
266
|
+
ignoreClose = true
|
|
267
|
+
try {
|
|
268
|
+
child.kill()
|
|
269
|
+
} catch {}
|
|
270
|
+
rejectPending("unknown", `mcp server "${serverName}" shutdown`, { phase: "shutdown" })
|
|
271
|
+
cleanupChild()
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function sendRequest(method, params = {}, { phase = "request", timeoutMs = requestTimeoutMs } = {}) {
|
|
275
|
+
await startProcess()
|
|
276
|
+
const id = nextId++
|
|
277
|
+
const payload = { jsonrpc: "2.0", id, method, params }
|
|
278
|
+
|
|
279
|
+
return new Promise((resolve, reject) => {
|
|
280
|
+
const startedAt = Date.now()
|
|
281
|
+
const timer = setTimeout(() => {
|
|
282
|
+
pending.delete(id)
|
|
283
|
+
reject(
|
|
284
|
+
new McpError(`mcp server "${serverName}" timed out after ${timeoutMs}ms on "${method}"`, {
|
|
285
|
+
reason: "timeout",
|
|
286
|
+
server: serverName,
|
|
287
|
+
action: method,
|
|
288
|
+
phase
|
|
289
|
+
})
|
|
290
|
+
)
|
|
291
|
+
}, timeoutMs)
|
|
292
|
+
|
|
293
|
+
pending.set(id, { resolve, reject, timer, method, phase, startedAt })
|
|
294
|
+
try {
|
|
295
|
+
const wireFraming = configuredFraming === "auto" ? activeFraming : configuredFraming
|
|
296
|
+
child.stdin.write(encodeRpcMessage(payload, wireFraming))
|
|
297
|
+
} catch (error) {
|
|
298
|
+
clearTimeout(timer)
|
|
299
|
+
pending.delete(id)
|
|
300
|
+
reject(
|
|
301
|
+
new McpError(`mcp server "${serverName}" stdin write failed: ${error.message}`, {
|
|
302
|
+
reason: "server_crash",
|
|
303
|
+
server: serverName,
|
|
304
|
+
action: method,
|
|
305
|
+
phase
|
|
306
|
+
})
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
})
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function sendNotification(method, params = {}) {
|
|
313
|
+
if (!child || lifecycle === "closed") return
|
|
314
|
+
const payload = { jsonrpc: "2.0", method, params }
|
|
315
|
+
try {
|
|
316
|
+
const wireFraming = configuredFraming === "auto" ? activeFraming : configuredFraming
|
|
317
|
+
child.stdin.write(encodeRpcMessage(payload, wireFraming))
|
|
318
|
+
} catch {
|
|
319
|
+
// best effort
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function initializeOnce() {
|
|
324
|
+
if (initialized) return
|
|
325
|
+
const initParams = {
|
|
326
|
+
protocolVersion: "2024-11-05",
|
|
327
|
+
capabilities: {},
|
|
328
|
+
clientInfo: { name: "kkcode", version: "0.1.2" }
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (configuredFraming === "auto") {
|
|
332
|
+
let lastError = null
|
|
333
|
+
for (const candidate of ["content-length", "newline"]) {
|
|
334
|
+
activeFraming = candidate
|
|
335
|
+
await shutdownProcess()
|
|
336
|
+
try {
|
|
337
|
+
await sendRequest("initialize", initParams, { phase: "initialize" })
|
|
338
|
+
sendNotification("notifications/initialized")
|
|
339
|
+
initialized = true
|
|
340
|
+
return
|
|
341
|
+
} catch (error) {
|
|
342
|
+
lastError = error
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
throw lastError || new McpError(`mcp server "${serverName}" failed to initialize`, {
|
|
346
|
+
reason: "unknown",
|
|
347
|
+
server: serverName,
|
|
348
|
+
phase: "initialize"
|
|
349
|
+
})
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
await sendRequest("initialize", initParams, { phase: "initialize" })
|
|
353
|
+
sendNotification("notifications/initialized")
|
|
354
|
+
initialized = true
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async function healthPingOrTools() {
|
|
358
|
+
if (healthCheckMethod === "ping") {
|
|
359
|
+
await sendRequest("ping", {}, { phase: "request" })
|
|
360
|
+
return
|
|
361
|
+
}
|
|
362
|
+
if (healthCheckMethod === "tools_list") {
|
|
363
|
+
await sendRequest("tools/list", {}, { phase: "request" })
|
|
364
|
+
return
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
await sendRequest("ping", {}, { phase: "request" })
|
|
369
|
+
} catch (error) {
|
|
370
|
+
if (!["bad_response", "protocol_error", "unknown"].includes(error.reason)) throw error
|
|
371
|
+
await sendRequest("tools/list", {}, { phase: "request" })
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
serverName,
|
|
377
|
+
transport: "stdio",
|
|
378
|
+
|
|
379
|
+
async health() {
|
|
380
|
+
try {
|
|
381
|
+
await initializeOnce()
|
|
382
|
+
await healthPingOrTools()
|
|
383
|
+
return {
|
|
384
|
+
ok: true,
|
|
385
|
+
reason: "ok",
|
|
386
|
+
framing: configuredFraming === "auto" ? activeFraming : configuredFraming
|
|
387
|
+
}
|
|
388
|
+
} catch (error) {
|
|
389
|
+
return {
|
|
390
|
+
ok: false,
|
|
391
|
+
error: error.message,
|
|
392
|
+
reason: error.reason || "unknown",
|
|
393
|
+
phase: error.details?.phase || "unknown",
|
|
394
|
+
framing: configuredFraming === "auto" ? activeFraming : configuredFraming
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
|
|
399
|
+
async listTools() {
|
|
400
|
+
await initializeOnce()
|
|
401
|
+
const out = await sendRequest("tools/list")
|
|
402
|
+
return Array.isArray(out?.tools) ? out.tools : []
|
|
403
|
+
},
|
|
404
|
+
|
|
405
|
+
async listPrompts() {
|
|
406
|
+
await initializeOnce()
|
|
407
|
+
try {
|
|
408
|
+
const out = await sendRequest("prompts/list")
|
|
409
|
+
return Array.isArray(out?.prompts) ? out.prompts : []
|
|
410
|
+
} catch {
|
|
411
|
+
return []
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
async getPrompt(name, args = {}) {
|
|
416
|
+
await initializeOnce()
|
|
417
|
+
return sendRequest("prompts/get", { name, arguments: args })
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
async listResources() {
|
|
421
|
+
await initializeOnce()
|
|
422
|
+
try {
|
|
423
|
+
const out = await sendRequest("resources/list")
|
|
424
|
+
return Array.isArray(out?.resources) ? out.resources : []
|
|
425
|
+
} catch {
|
|
426
|
+
return []
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
async listTemplates() {
|
|
431
|
+
await initializeOnce()
|
|
432
|
+
try {
|
|
433
|
+
const out = await sendRequest("resources/templates/list")
|
|
434
|
+
return Array.isArray(out?.templates) ? out.templates : []
|
|
435
|
+
} catch {
|
|
436
|
+
return []
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
|
|
440
|
+
async callTool(name, args = {}, signal = null) {
|
|
441
|
+
await initializeOnce()
|
|
442
|
+
const result = await sendRequest("tools/call", { name, arguments: args })
|
|
443
|
+
return normalizeToolResult(result, serverName, name)
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
shutdown() {
|
|
447
|
+
sendNotification("notifications/cancelled", { reason: "shutdown" })
|
|
448
|
+
shutdownProcess().catch(() => {})
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|