ladder-mcp 1.0.0 → 1.0.1
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/CHANGELOG.md +26 -0
- package/README.md +35 -16
- package/dist/index.js +5 -2
- package/dist/transports/acp.js +26 -3
- package/dist/transports/cli-admin.js +31 -3
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@ All notable changes to Ladder_mcp are documented here. Format loosely follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/); this project uses
|
|
5
5
|
[Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [1.0.1] - 2026-06-28
|
|
8
|
+
|
|
9
|
+
Bug-fix release. Issues found by exercising all 20 tools live against a real
|
|
10
|
+
Kimi CLI (the mock-only unit tests had missed them).
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **ACP responses lost spaces** (`kimi_chat`): streamed token chunks were each
|
|
15
|
+
trimmed and newline-joined, so `"Two plus two equals four."` came back as
|
|
16
|
+
`"Twoplustwoequalsfour."`. Text fragments are now concatenated as-is and only
|
|
17
|
+
the final string is trimmed. (CLI tools `kimi_analyze`/`kimi_resume` were never
|
|
18
|
+
affected.)
|
|
19
|
+
- **`kimi_export_session` silently no-op'd** while reporting `ok: true`: when no
|
|
20
|
+
session id was given, `kimi export` defaults to the most recent session and asks
|
|
21
|
+
`Export previous session …? [Y/n]` — a prompt that `-y` does not suppress in Kimi
|
|
22
|
+
CLI 0.20.1, so with stdin closed it exited 0 without writing. The tool now
|
|
23
|
+
resolves the most recent session id itself and passes it explicitly (skipping the
|
|
24
|
+
prompt), always passes `-y`, and verifies the output file exists before reporting
|
|
25
|
+
success.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- `kimi_acp_sessions` now accepts `limit` (default 20) and `work_dir` filters,
|
|
30
|
+
for parity with `kimi_list_sessions`; previously it dumped every ACP session
|
|
31
|
+
across all projects in one response.
|
|
32
|
+
|
|
7
33
|
## [1.0.0] - 2026-06-27
|
|
8
34
|
|
|
9
35
|
First release. Windows-first MCP bridge for Kimi Code CLI v24, rebuilt in a
|
package/README.md
CHANGED
|
@@ -6,7 +6,8 @@ client like Claude Code can run codebase analysis, native sessions, API
|
|
|
6
6
|
queries, ACP chat, background tasks, and CLI admin/diagnostics — all on Windows
|
|
7
7
|
without hardcoded POSIX assumptions.
|
|
8
8
|
|
|
9
|
-
> Status: **v1.0.
|
|
9
|
+
> Status: **v1.0.1** ([npm](https://www.npmjs.com/package/ladder-mcp)).
|
|
10
|
+
> Supported platform is **Windows 11 only**.
|
|
10
11
|
|
|
11
12
|
## Requirements
|
|
12
13
|
|
|
@@ -15,40 +16,58 @@ without hardcoded POSIX assumptions.
|
|
|
15
16
|
- Kimi Code CLI installed (`kimi.exe` on PATH or at `~/.kimi-code/bin/kimi.exe`),
|
|
16
17
|
authenticated (`~/.kimi-code/`)
|
|
17
18
|
|
|
18
|
-
##
|
|
19
|
+
## Quick start (from npm)
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
npm run build # compiles src/ -> dist/ (tests excluded)
|
|
23
|
-
```
|
|
21
|
+
You don't need to clone or build — the package is published on npm and your MCP
|
|
22
|
+
client launches it via `npx`.
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
**Claude Code (one command):**
|
|
26
25
|
|
|
27
26
|
```bash
|
|
28
|
-
|
|
29
|
-
npm run typecheck # tsc --noEmit (incl. tests)
|
|
30
|
-
npm run dev # run the server from source via tsx
|
|
27
|
+
claude mcp add kimi-code -- npx -y ladder-mcp
|
|
31
28
|
```
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
The server speaks MCP over stdio. Point your MCP client at the built entry:
|
|
30
|
+
**Or add it manually to your MCP config:**
|
|
36
31
|
|
|
37
32
|
```jsonc
|
|
38
|
-
// e.g. Claude Code MCP config
|
|
39
33
|
{
|
|
40
34
|
"mcpServers": {
|
|
41
35
|
"kimi-code": {
|
|
42
|
-
"command": "
|
|
43
|
-
"args": ["
|
|
36
|
+
"command": "npx",
|
|
37
|
+
"args": ["-y", "ladder-mcp"]
|
|
44
38
|
}
|
|
45
39
|
}
|
|
46
40
|
}
|
|
47
41
|
```
|
|
48
42
|
|
|
43
|
+
Then in Claude Code run `/mcp` (should show `kimi-code: connected`) and call
|
|
44
|
+
`kimi_status` to confirm the environment is detected.
|
|
45
|
+
|
|
46
|
+
> The server speaks MCP over **stdio**: it is launched and managed by the client,
|
|
47
|
+
> not run by hand. Running `npx ladder-mcp` directly will appear to "hang" — that
|
|
48
|
+
> is the server correctly waiting for a client. Exit with Ctrl+C.
|
|
49
|
+
|
|
50
|
+
Prefer a global install? `npm install -g ladder-mcp`, then use `ladder-mcp` as the
|
|
51
|
+
command instead of `npx -y ladder-mcp`.
|
|
52
|
+
|
|
49
53
|
To let Kimi Code itself host this server, use the `kimi_generate_mcp_config`
|
|
50
54
|
tool to produce/merge a `.kimi-code/mcp.json` entry.
|
|
51
55
|
|
|
56
|
+
## Build from source (contributors)
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install
|
|
60
|
+
npm run build # compiles src/ -> dist/ (tests excluded)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Quick checks:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm test # vitest (50 tests)
|
|
67
|
+
npm run typecheck # tsc --noEmit (incl. tests)
|
|
68
|
+
npm run dev # run the server from source via tsx
|
|
69
|
+
```
|
|
70
|
+
|
|
52
71
|
## Tools
|
|
53
72
|
|
|
54
73
|
**Core (v1)** — `kimi_analyze`, `kimi_query`, `kimi_verify`, `kimi_resume`,
|
package/dist/index.js
CHANGED
|
@@ -219,8 +219,11 @@ server.tool('kimi_chat', 'Send a prompt through Kimi ACP over stdio. Set backgro
|
|
|
219
219
|
});
|
|
220
220
|
return textResponse(JSON.stringify(result, null, 2), !result.ok);
|
|
221
221
|
});
|
|
222
|
-
server.tool('kimi_acp_sessions', 'List Kimi ACP sessions through `session/list`.', {
|
|
223
|
-
|
|
222
|
+
server.tool('kimi_acp_sessions', 'List Kimi ACP sessions through `session/list`.', {
|
|
223
|
+
limit: z.number().int().positive().optional(),
|
|
224
|
+
work_dir: z.string().optional(),
|
|
225
|
+
}, async ({ limit, work_dir }) => {
|
|
226
|
+
const result = await listAcpSessions({ limit, workDir: work_dir });
|
|
224
227
|
return textResponse(result.ok ? result.text : `Error: ${result.error}`, !result.ok);
|
|
225
228
|
});
|
|
226
229
|
server.tool('kimi_cancel', 'Cancel an active Ladder task by task_id or a Kimi ACP session by session_id.', {
|
package/dist/transports/acp.js
CHANGED
|
@@ -153,7 +153,11 @@ function extractTextDeep(value) {
|
|
|
153
153
|
return [...direct, ...nested];
|
|
154
154
|
}
|
|
155
155
|
export function extractAcpText(value) {
|
|
156
|
-
|
|
156
|
+
// Concatenate text fragments as-is. Kimi streams the answer as many small
|
|
157
|
+
// agent_message_chunk tokens (e.g. "Two", " plus", " two"); trimming each
|
|
158
|
+
// fragment or joining on newlines would drop the spaces between tokens and
|
|
159
|
+
// run words together. Callers trim the final assembled string.
|
|
160
|
+
return extractTextDeep(value).join('');
|
|
157
161
|
}
|
|
158
162
|
// Keep the trailing portion of `text` within a UTF-8 *byte* budget. Slicing by
|
|
159
163
|
// String length (UTF-16 code units) under-counts multibyte characters, so the
|
|
@@ -357,7 +361,9 @@ export async function runAcpPrompt(options) {
|
|
|
357
361
|
sessionId = extractSessionId(await client.newSession(options.workDir));
|
|
358
362
|
}
|
|
359
363
|
const result = await client.prompt(sessionId, options.prompt, options.workDir);
|
|
360
|
-
|
|
364
|
+
// extractAcpText no longer trims fragments (to preserve inter-token spaces),
|
|
365
|
+
// so trim the fully assembled string here. getUpdateText already trims.
|
|
366
|
+
let text = extractAcpText(result).trim() || client.getUpdateText();
|
|
361
367
|
if (!text) {
|
|
362
368
|
try {
|
|
363
369
|
text = JSON.stringify(result, null, 2);
|
|
@@ -385,11 +391,28 @@ export async function runAcpPrompt(options) {
|
|
|
385
391
|
client.close();
|
|
386
392
|
}
|
|
387
393
|
}
|
|
388
|
-
|
|
394
|
+
const DEFAULT_ACP_SESSION_LIMIT = 20;
|
|
395
|
+
function normalizePath(value) {
|
|
396
|
+
return value.replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase();
|
|
397
|
+
}
|
|
398
|
+
export async function listAcpSessions(options = {}) {
|
|
389
399
|
const client = new AcpClient(60_000);
|
|
390
400
|
try {
|
|
391
401
|
await client.initialize();
|
|
392
402
|
const result = await client.listSessions();
|
|
403
|
+
// Filter by working directory and cap the count for parity with kimi_list_sessions;
|
|
404
|
+
// session/list otherwise returns every ACP session across all projects in one blob.
|
|
405
|
+
const sessions = result?.sessions;
|
|
406
|
+
if (Array.isArray(sessions)) {
|
|
407
|
+
let filtered = sessions;
|
|
408
|
+
if (options.workDir) {
|
|
409
|
+
const target = normalizePath(options.workDir);
|
|
410
|
+
filtered = filtered.filter((s) => typeof s?.cwd === 'string' && normalizePath(s.cwd) === target);
|
|
411
|
+
}
|
|
412
|
+
const limit = options.limit ?? DEFAULT_ACP_SESSION_LIMIT;
|
|
413
|
+
const limited = limit > 0 ? filtered.slice(0, limit) : filtered;
|
|
414
|
+
return { ok: true, text: JSON.stringify({ sessions: limited, total: filtered.length }, null, 2) };
|
|
415
|
+
}
|
|
393
416
|
return { ok: true, text: JSON.stringify(result, null, 2) };
|
|
394
417
|
}
|
|
395
418
|
catch (error) {
|
|
@@ -3,6 +3,7 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
5
|
import { buildKimiEnv, getKimiStatus, resolveKimiPaths } from '../environment.js';
|
|
6
|
+
import { listSessions } from '../session-store.js';
|
|
6
7
|
const execFileAsync = promisify(execFile);
|
|
7
8
|
const ADMIN_TIMEOUT_MS = 30_000;
|
|
8
9
|
// Quote a single argument for the human-readable preview command. This string is
|
|
@@ -76,8 +77,12 @@ export function buildExportArgs(options) {
|
|
|
76
77
|
if (options.sessionId)
|
|
77
78
|
args.push(options.sessionId);
|
|
78
79
|
args.push('-o', options.outputPath);
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
// Always auto-confirm. `kimi export` prompts "Export previous session …? [Y/n]"
|
|
81
|
+
// even for a brand-new path; since we close stdin, that prompt would get EOF and
|
|
82
|
+
// the export would silently no-op while still exiting 0. Overwrite safety is
|
|
83
|
+
// enforced by our own pre-check (assertSafeOutputPath + statSync + overwrite_existing),
|
|
84
|
+
// so unconditionally passing -y is safe.
|
|
85
|
+
args.push('-y');
|
|
81
86
|
if (options.includeGlobalLog !== true)
|
|
82
87
|
args.push('--no-include-global-log');
|
|
83
88
|
return args;
|
|
@@ -179,6 +184,18 @@ export async function exportKimiSession(options) {
|
|
|
179
184
|
error: error instanceof Error ? error.message : String(error),
|
|
180
185
|
};
|
|
181
186
|
}
|
|
187
|
+
// Resolve an explicit session id ourselves when the caller omitted one. `kimi export`
|
|
188
|
+
// otherwise defaults to "the most recent session" and asks "Export previous session …?
|
|
189
|
+
// [Y/n]" — a prompt that `-y` does NOT suppress in Kimi CLI 0.20.1, so with stdin closed
|
|
190
|
+
// it hangs/aborts and writes nothing. Passing an explicit id skips that prompt entirely.
|
|
191
|
+
let sessionId = options.sessionId;
|
|
192
|
+
if (!sessionId) {
|
|
193
|
+
const recent = listSessions({ limit: 1 })[0];
|
|
194
|
+
if (!recent) {
|
|
195
|
+
return { ok: false, stdout: '', stderr: '', error: 'No Kimi session found to export. Provide an explicit session_id.' };
|
|
196
|
+
}
|
|
197
|
+
sessionId = recent.id;
|
|
198
|
+
}
|
|
182
199
|
const outputPath = path.resolve(options.outputPath);
|
|
183
200
|
let existingStat;
|
|
184
201
|
try {
|
|
@@ -207,7 +224,18 @@ export async function exportKimiSession(options) {
|
|
|
207
224
|
};
|
|
208
225
|
}
|
|
209
226
|
}
|
|
210
|
-
|
|
227
|
+
const result = await runKimiCommand(buildExportArgs({ ...options, sessionId, outputPath }), 120_000);
|
|
228
|
+
// The CLI can exit 0 without producing the archive (e.g. a declined/aborted prompt).
|
|
229
|
+
// Verify the file actually exists so we never report success for a no-op export.
|
|
230
|
+
if (result.ok && !fs.existsSync(outputPath)) {
|
|
231
|
+
return {
|
|
232
|
+
ok: false,
|
|
233
|
+
stdout: result.stdout,
|
|
234
|
+
stderr: result.stderr,
|
|
235
|
+
error: 'Kimi export exited without creating the output file. No archive was written.',
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return result;
|
|
211
239
|
}
|
|
212
240
|
export function visualizeSession(options) {
|
|
213
241
|
const binary = requireBinary();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ladder-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Windows-first MCP bridge for Kimi Code CLI v24",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"build": "tsc -p tsconfig.build.json",
|
|
20
20
|
"start": "node dist/index.js",
|
|
21
21
|
"test": "vitest run",
|
|
22
|
-
"typecheck": "tsc --noEmit"
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"prepublishOnly": "npm run build && npm test"
|
|
23
24
|
},
|
|
24
25
|
"engines": {
|
|
25
26
|
"node": ">=18.0.0"
|