krexel 0.1.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 +94 -0
- package/bin/krexel +10 -0
- package/dist/cli.d.ts +21 -0
- package/dist/cli.js +167 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-patchers.d.ts +108 -0
- package/dist/config-patchers.js +231 -0
- package/dist/config-patchers.js.map +1 -0
- package/dist/init.d.ts +103 -0
- package/dist/init.js +312 -0
- package/dist/init.js.map +1 -0
- package/dist/login.d.ts +29 -0
- package/dist/login.js +106 -0
- package/dist/login.js.map +1 -0
- package/dist/logs.d.ts +10 -0
- package/dist/logs.js +26 -0
- package/dist/logs.js.map +1 -0
- package/dist/mcp-client.d.ts +168 -0
- package/dist/mcp-client.js +379 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/output.d.ts +44 -0
- package/dist/output.js +90 -0
- package/dist/output.js.map +1 -0
- package/dist/paths.d.ts +69 -0
- package/dist/paths.js +115 -0
- package/dist/paths.js.map +1 -0
- package/dist/rollback.d.ts +10 -0
- package/dist/rollback.js +34 -0
- package/dist/rollback.js.map +1 -0
- package/dist/ship.d.ts +13 -0
- package/dist/ship.js +49 -0
- package/dist/ship.js.map +1 -0
- package/dist/whoami.d.ts +7 -0
- package/dist/whoami.js +25 -0
- package/dist/whoami.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp-client.ts — stdio JSON-RPC client for the Krexel MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Why hand-rolled and not @modelcontextprotocol/sdk?
|
|
5
|
+
* - The CLI is thin: it only needs `initialize` + `tools/call`.
|
|
6
|
+
* - Hand-rolling makes the binary-not-found error deterministic and lets us
|
|
7
|
+
* pin the exact failure message the brief calls for.
|
|
8
|
+
* - Avoids dragging a second copy of the SDK into the CLI's bundle.
|
|
9
|
+
*
|
|
10
|
+
* Wire format (Model Context Protocol):
|
|
11
|
+
* <Content-Length: N>\r\n\r\n<N bytes of JSON>
|
|
12
|
+
*
|
|
13
|
+
* Lifecycle:
|
|
14
|
+
* start() → resolves once the MCP server has replied to `initialize`.
|
|
15
|
+
* callTool(name, args) → returns the parsed `result.content[0].text` JSON.
|
|
16
|
+
* stop() → closes the child process; idempotent.
|
|
17
|
+
*
|
|
18
|
+
* Resolving the MCP server binary (in order):
|
|
19
|
+
* 1. $KREXEL_MCP_BIN — full command, e.g. `node /abs/path/to/mcp-server/dist/index.js`
|
|
20
|
+
* 2. `krexel-mcp` on $PATH (preferred for installed users)
|
|
21
|
+
* 3. Sibling `../mcp-server/dist/index.js` (monorepo dev fallback)
|
|
22
|
+
* If none of those exist, throw the install hint — never fake success.
|
|
23
|
+
*/
|
|
24
|
+
import { z } from "zod";
|
|
25
|
+
import { nanoid } from "nanoid";
|
|
26
|
+
declare const JsonRpcRequestSchema: z.ZodObject<{
|
|
27
|
+
jsonrpc: z.ZodLiteral<"2.0">;
|
|
28
|
+
id: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
|
|
29
|
+
method: z.ZodString;
|
|
30
|
+
params: z.ZodOptional<z.ZodUnknown>;
|
|
31
|
+
}, "strip", z.ZodTypeAny, {
|
|
32
|
+
method: string;
|
|
33
|
+
jsonrpc: "2.0";
|
|
34
|
+
id: string | number;
|
|
35
|
+
params?: unknown;
|
|
36
|
+
}, {
|
|
37
|
+
method: string;
|
|
38
|
+
jsonrpc: "2.0";
|
|
39
|
+
id: string | number;
|
|
40
|
+
params?: unknown;
|
|
41
|
+
}>;
|
|
42
|
+
export type JsonRpcRequest = z.infer<typeof JsonRpcRequestSchema>;
|
|
43
|
+
declare const JsonRpcResponseSchema: z.ZodObject<{
|
|
44
|
+
jsonrpc: z.ZodLiteral<"2.0">;
|
|
45
|
+
id: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
|
|
46
|
+
result: z.ZodOptional<z.ZodUnknown>;
|
|
47
|
+
error: z.ZodOptional<z.ZodObject<{
|
|
48
|
+
code: z.ZodNumber;
|
|
49
|
+
message: z.ZodString;
|
|
50
|
+
data: z.ZodOptional<z.ZodUnknown>;
|
|
51
|
+
}, "strip", z.ZodTypeAny, {
|
|
52
|
+
code: number;
|
|
53
|
+
message: string;
|
|
54
|
+
data?: unknown;
|
|
55
|
+
}, {
|
|
56
|
+
code: number;
|
|
57
|
+
message: string;
|
|
58
|
+
data?: unknown;
|
|
59
|
+
}>>;
|
|
60
|
+
}, "strip", z.ZodTypeAny, {
|
|
61
|
+
jsonrpc: "2.0";
|
|
62
|
+
id: string | number;
|
|
63
|
+
result?: unknown;
|
|
64
|
+
error?: {
|
|
65
|
+
code: number;
|
|
66
|
+
message: string;
|
|
67
|
+
data?: unknown;
|
|
68
|
+
} | undefined;
|
|
69
|
+
}, {
|
|
70
|
+
jsonrpc: "2.0";
|
|
71
|
+
id: string | number;
|
|
72
|
+
result?: unknown;
|
|
73
|
+
error?: {
|
|
74
|
+
code: number;
|
|
75
|
+
message: string;
|
|
76
|
+
data?: unknown;
|
|
77
|
+
} | undefined;
|
|
78
|
+
}>;
|
|
79
|
+
type JsonRpcResponse = z.infer<typeof JsonRpcResponseSchema>;
|
|
80
|
+
/** Shape returned by the MCP server's CallToolResult — see mcp-server/src/index.ts. */
|
|
81
|
+
declare const CallToolResultSchema: z.ZodObject<{
|
|
82
|
+
content: z.ZodArray<z.ZodObject<{
|
|
83
|
+
type: z.ZodLiteral<"text">;
|
|
84
|
+
text: z.ZodString;
|
|
85
|
+
}, "strip", z.ZodTypeAny, {
|
|
86
|
+
text: string;
|
|
87
|
+
type: "text";
|
|
88
|
+
}, {
|
|
89
|
+
text: string;
|
|
90
|
+
type: "text";
|
|
91
|
+
}>, "many">;
|
|
92
|
+
isError: z.ZodOptional<z.ZodBoolean>;
|
|
93
|
+
}, "strip", z.ZodTypeAny, {
|
|
94
|
+
content: {
|
|
95
|
+
text: string;
|
|
96
|
+
type: "text";
|
|
97
|
+
}[];
|
|
98
|
+
isError?: boolean | undefined;
|
|
99
|
+
}, {
|
|
100
|
+
content: {
|
|
101
|
+
text: string;
|
|
102
|
+
type: "text";
|
|
103
|
+
}[];
|
|
104
|
+
isError?: boolean | undefined;
|
|
105
|
+
}>;
|
|
106
|
+
export type CallToolResult = z.infer<typeof CallToolResultSchema>;
|
|
107
|
+
export interface McpServerLocation {
|
|
108
|
+
command: string;
|
|
109
|
+
args: string[];
|
|
110
|
+
/** Path/command used to derive the location — for diagnostic messages. */
|
|
111
|
+
source: string;
|
|
112
|
+
}
|
|
113
|
+
/** Determine how to launch the MCP server. Throws the install hint if none found. */
|
|
114
|
+
export declare function resolveMcpServer(envOverride?: NodeJS.ProcessEnv, pathOverride?: string, execPath?: string): McpServerLocation;
|
|
115
|
+
/** The error called for in the brief — fired by resolveMcpServer when nothing is installed. */
|
|
116
|
+
export declare class McpServerNotFoundError extends Error {
|
|
117
|
+
constructor();
|
|
118
|
+
}
|
|
119
|
+
/** A client is single-shot: one process, one initialize, many calls, then stop. */
|
|
120
|
+
export declare class KrexelMcpClient {
|
|
121
|
+
private child;
|
|
122
|
+
private buf;
|
|
123
|
+
private pending;
|
|
124
|
+
private nextId;
|
|
125
|
+
private initDone;
|
|
126
|
+
private location;
|
|
127
|
+
/**
|
|
128
|
+
* Start the MCP server and complete the handshake.
|
|
129
|
+
* Resolves once initialize has been ack'd; throws on spawn failure.
|
|
130
|
+
*/
|
|
131
|
+
start(): Promise<void>;
|
|
132
|
+
/** MCP `initialize` handshake — exchanges protocol version + capabilities. */
|
|
133
|
+
private handshake;
|
|
134
|
+
/**
|
|
135
|
+
* Call an MCP tool. The MCP server's tool implementations always return a
|
|
136
|
+
* `content` array with a single text item containing JSON-encoded payload.
|
|
137
|
+
* We parse that JSON here and return the typed object.
|
|
138
|
+
*/
|
|
139
|
+
callTool<T = unknown>(name: string, args?: Record<string, unknown>): Promise<T>;
|
|
140
|
+
/** Gracefully terminate the MCP process. Idempotent. */
|
|
141
|
+
stop(): void;
|
|
142
|
+
/**
|
|
143
|
+
* Read framed MCP messages off stdout. Frame = "Content-Length: N\r\n\r\n<body>".
|
|
144
|
+
* Extra leading bytes (mcp server's debug noise on broken setups, etc.) are
|
|
145
|
+
* skipped; we don't expect any here because the server uses stderr for logs.
|
|
146
|
+
*/
|
|
147
|
+
private onStdout;
|
|
148
|
+
private handleFrame;
|
|
149
|
+
private send;
|
|
150
|
+
/**
|
|
151
|
+
* Build a Content-Length framed JSON-RPC request and write it to the
|
|
152
|
+
* child's stdin. Public-and-testable so the wire shape can be unit-tested
|
|
153
|
+
* without spawning the real MCP server.
|
|
154
|
+
*
|
|
155
|
+
* Exposed for tests — not part of the public API callers should depend on.
|
|
156
|
+
*/
|
|
157
|
+
writeFrame(request: JsonRpcRequest, idStr: string): Promise<JsonRpcResponse>;
|
|
158
|
+
/**
|
|
159
|
+
* Fire-and-forget notification (no id, no response). Used for
|
|
160
|
+
* `notifications/initialized` per the MCP spec.
|
|
161
|
+
*/
|
|
162
|
+
private notify;
|
|
163
|
+
private failAllPending;
|
|
164
|
+
/** Diagnostic: which binary is in use. Useful for `krexel whoami` and tests. */
|
|
165
|
+
describeServer(): string;
|
|
166
|
+
}
|
|
167
|
+
export declare function getClient(): KrexelMcpClient;
|
|
168
|
+
export { nanoid };
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp-client.ts — stdio JSON-RPC client for the Krexel MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Why hand-rolled and not @modelcontextprotocol/sdk?
|
|
5
|
+
* - The CLI is thin: it only needs `initialize` + `tools/call`.
|
|
6
|
+
* - Hand-rolling makes the binary-not-found error deterministic and lets us
|
|
7
|
+
* pin the exact failure message the brief calls for.
|
|
8
|
+
* - Avoids dragging a second copy of the SDK into the CLI's bundle.
|
|
9
|
+
*
|
|
10
|
+
* Wire format (Model Context Protocol):
|
|
11
|
+
* <Content-Length: N>\r\n\r\n<N bytes of JSON>
|
|
12
|
+
*
|
|
13
|
+
* Lifecycle:
|
|
14
|
+
* start() → resolves once the MCP server has replied to `initialize`.
|
|
15
|
+
* callTool(name, args) → returns the parsed `result.content[0].text` JSON.
|
|
16
|
+
* stop() → closes the child process; idempotent.
|
|
17
|
+
*
|
|
18
|
+
* Resolving the MCP server binary (in order):
|
|
19
|
+
* 1. $KREXEL_MCP_BIN — full command, e.g. `node /abs/path/to/mcp-server/dist/index.js`
|
|
20
|
+
* 2. `krexel-mcp` on $PATH (preferred for installed users)
|
|
21
|
+
* 3. Sibling `../mcp-server/dist/index.js` (monorepo dev fallback)
|
|
22
|
+
* If none of those exist, throw the install hint — never fake success.
|
|
23
|
+
*/
|
|
24
|
+
import { spawn } from "node:child_process";
|
|
25
|
+
import { existsSync } from "node:fs";
|
|
26
|
+
import * as path from "node:path";
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
28
|
+
import { z } from "zod";
|
|
29
|
+
import { nanoid } from "nanoid";
|
|
30
|
+
import { printError } from "./output.js";
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// JSON-RPC types — minimal, just enough to talk MCP initialize + tools/call.
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
const JsonRpcRequestSchema = z.object({
|
|
35
|
+
jsonrpc: z.literal("2.0"),
|
|
36
|
+
id: z.union([z.string(), z.number()]),
|
|
37
|
+
method: z.string(),
|
|
38
|
+
params: z.unknown().optional(),
|
|
39
|
+
});
|
|
40
|
+
const JsonRpcResponseSchema = z.object({
|
|
41
|
+
jsonrpc: z.literal("2.0"),
|
|
42
|
+
id: z.union([z.string(), z.number()]),
|
|
43
|
+
result: z.unknown().optional(),
|
|
44
|
+
error: z
|
|
45
|
+
.object({
|
|
46
|
+
code: z.number(),
|
|
47
|
+
message: z.string(),
|
|
48
|
+
data: z.unknown().optional(),
|
|
49
|
+
})
|
|
50
|
+
.optional(),
|
|
51
|
+
});
|
|
52
|
+
/** Shape returned by the MCP server's CallToolResult — see mcp-server/src/index.ts. */
|
|
53
|
+
const CallToolResultSchema = z.object({
|
|
54
|
+
content: z.array(z.object({
|
|
55
|
+
type: z.literal("text"),
|
|
56
|
+
text: z.string(),
|
|
57
|
+
})),
|
|
58
|
+
isError: z.boolean().optional(),
|
|
59
|
+
});
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Locate the MCP server binary.
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
64
|
+
// From dist/mcp-client.js we look up to ../mcp-server/dist/index.js (built
|
|
65
|
+
// output). From src/mcp-client.js we look up to ../mcp-server/dist/index.js too
|
|
66
|
+
// — but `dist` may not exist in dev. We tolerate that and bail loudly.
|
|
67
|
+
const DEFAULT_SIBLING_BIN = path.resolve(HERE, "../../mcp-server/dist/index.js");
|
|
68
|
+
/** Test seam — override the location of the monorepo-sibling built binary. */
|
|
69
|
+
function siblingBin() {
|
|
70
|
+
return process.env.KREXEL_MCP_SIBLING ?? DEFAULT_SIBLING_BIN;
|
|
71
|
+
}
|
|
72
|
+
function lookupSiblingBin() {
|
|
73
|
+
const candidate = siblingBin();
|
|
74
|
+
if (!existsSync(candidate))
|
|
75
|
+
return null;
|
|
76
|
+
return {
|
|
77
|
+
command: process.execPath,
|
|
78
|
+
args: [candidate],
|
|
79
|
+
source: candidate,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function lookupOnPath(name) {
|
|
83
|
+
const pathEnv = process.env.PATH ?? "";
|
|
84
|
+
const sep = process.platform === "win32" ? ";" : ":";
|
|
85
|
+
for (const dir of pathEnv.split(sep)) {
|
|
86
|
+
if (!dir)
|
|
87
|
+
continue;
|
|
88
|
+
const candidate = path.join(dir, name);
|
|
89
|
+
if (existsSync(candidate)) {
|
|
90
|
+
return { command: candidate, args: [], source: candidate };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
/** Determine how to launch the MCP server. Throws the install hint if none found. */
|
|
96
|
+
export function resolveMcpServer(envOverride = process.env, pathOverride = process.env.PATH ?? "", execPath = process.execPath) {
|
|
97
|
+
// 1. Caller-supplied full command — full freedom, used by tests + advanced setups.
|
|
98
|
+
if (envOverride.KREXEL_MCP_BIN) {
|
|
99
|
+
const parts = envOverride.KREXEL_MCP_BIN.split(/\s+/).filter(Boolean);
|
|
100
|
+
const cmd = parts[0];
|
|
101
|
+
if (!cmd)
|
|
102
|
+
throwMcpNotFound();
|
|
103
|
+
return {
|
|
104
|
+
command: cmd,
|
|
105
|
+
args: parts.slice(1),
|
|
106
|
+
source: "$KREXEL_MCP_BIN",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// 2. Same process node + sibling built binary (works in dev + when `npm link`ed).
|
|
110
|
+
if (execPath && existsSync(siblingBin())) {
|
|
111
|
+
return {
|
|
112
|
+
command: execPath,
|
|
113
|
+
args: [siblingBin()],
|
|
114
|
+
source: siblingBin(),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// 3. `krexel-mcp` on PATH (post `npm i -g krexel-mcp`).
|
|
118
|
+
// We override PATH lookups with the test path if provided.
|
|
119
|
+
const savedPath = process.env.PATH;
|
|
120
|
+
try {
|
|
121
|
+
process.env.PATH = pathOverride || savedPath || "";
|
|
122
|
+
const found = lookupOnPath("krexel-mcp");
|
|
123
|
+
if (found)
|
|
124
|
+
return { ...found, source: "krexel-mcp (PATH)" };
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
process.env.PATH = savedPath;
|
|
128
|
+
}
|
|
129
|
+
throwMcpNotFound();
|
|
130
|
+
}
|
|
131
|
+
/** The error called for in the brief — fired by resolveMcpServer when nothing is installed. */
|
|
132
|
+
export class McpServerNotFoundError extends Error {
|
|
133
|
+
constructor() {
|
|
134
|
+
super("Install the Krexel MCP server first: `npm i -g krexel-mcp`. " +
|
|
135
|
+
"Or build the monorepo sibling: `cd ~/krexel/mcp-server && npm run build`.");
|
|
136
|
+
this.name = "McpServerNotFoundError";
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function throwMcpNotFound() {
|
|
140
|
+
throw new McpServerNotFoundError();
|
|
141
|
+
}
|
|
142
|
+
/** A client is single-shot: one process, one initialize, many calls, then stop. */
|
|
143
|
+
export class KrexelMcpClient {
|
|
144
|
+
child = null;
|
|
145
|
+
buf = Buffer.alloc(0);
|
|
146
|
+
pending = new Map();
|
|
147
|
+
nextId = 1;
|
|
148
|
+
initDone = null;
|
|
149
|
+
location = null;
|
|
150
|
+
/**
|
|
151
|
+
* Start the MCP server and complete the handshake.
|
|
152
|
+
* Resolves once initialize has been ack'd; throws on spawn failure.
|
|
153
|
+
*/
|
|
154
|
+
start() {
|
|
155
|
+
if (this.initDone)
|
|
156
|
+
return this.initDone;
|
|
157
|
+
this.location = resolveMcpServer();
|
|
158
|
+
this.child = spawn(this.location.command, this.location.args, {
|
|
159
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
160
|
+
env: process.env,
|
|
161
|
+
});
|
|
162
|
+
// Any spawn error (ENOENT, EACCES) lands here — the briefest possible message.
|
|
163
|
+
this.child.on("error", (err) => {
|
|
164
|
+
this.failAllPending(err);
|
|
165
|
+
});
|
|
166
|
+
// Surface server-side stderr for diagnostics — the MCP server uses stderr
|
|
167
|
+
// for its "[krexel-mcp] ready" line.
|
|
168
|
+
const child = this.child;
|
|
169
|
+
if (child.stderr) {
|
|
170
|
+
child.stderr.on("data", (chunk) => {
|
|
171
|
+
const s = chunk.toString("utf8").trimEnd();
|
|
172
|
+
if (s)
|
|
173
|
+
process.stderr.write(`[mcp] ${s}\n`);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (!child.stdout) {
|
|
177
|
+
throw new Error("MCP server stdout is not a pipe — cannot continue");
|
|
178
|
+
}
|
|
179
|
+
child.stdout.on("data", (chunk) => this.onStdout(chunk));
|
|
180
|
+
child.on("exit", (code) => {
|
|
181
|
+
// Drain any pending requests so callers see a clean failure rather than
|
|
182
|
+
// a hang. We only fail-not-reject if startup completed; if the server
|
|
183
|
+
// died during handshake, callers were already going to see spawn errors.
|
|
184
|
+
if (this.initDone)
|
|
185
|
+
this.failAllPending(new Error(`MCP server exited (code ${code ?? "?"})`));
|
|
186
|
+
});
|
|
187
|
+
this.initDone = this.handshake();
|
|
188
|
+
return this.initDone;
|
|
189
|
+
}
|
|
190
|
+
/** MCP `initialize` handshake — exchanges protocol version + capabilities. */
|
|
191
|
+
async handshake() {
|
|
192
|
+
const res = await this.send("initialize", {
|
|
193
|
+
protocolVersion: "2024-11-05",
|
|
194
|
+
capabilities: {},
|
|
195
|
+
clientInfo: { name: "krexel-cli", version: "0.1.0" },
|
|
196
|
+
});
|
|
197
|
+
if (res.error)
|
|
198
|
+
throw new Error(`MCP initialize failed: ${res.error.message}`);
|
|
199
|
+
// Per spec, send `notifications/initialized` to tell the server we're ready.
|
|
200
|
+
this.notify("notifications/initialized", {});
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Call an MCP tool. The MCP server's tool implementations always return a
|
|
204
|
+
* `content` array with a single text item containing JSON-encoded payload.
|
|
205
|
+
* We parse that JSON here and return the typed object.
|
|
206
|
+
*/
|
|
207
|
+
async callTool(name, args = {}) {
|
|
208
|
+
if (!this.initDone)
|
|
209
|
+
await this.start();
|
|
210
|
+
if (!this.child || !this.child.stdout)
|
|
211
|
+
throw new Error("MCP client not started");
|
|
212
|
+
const res = await this.send("tools/call", {
|
|
213
|
+
name,
|
|
214
|
+
arguments: args,
|
|
215
|
+
});
|
|
216
|
+
if (res.error) {
|
|
217
|
+
throw new Error(`MCP tool ${name} failed: ${res.error.message}`);
|
|
218
|
+
}
|
|
219
|
+
const parsed = CallToolResultSchema.parse(res.result);
|
|
220
|
+
if (parsed.isError) {
|
|
221
|
+
const text = parsed.content[0]?.text ?? "(no error message)";
|
|
222
|
+
throw new Error(`MCP tool ${name} returned error: ${text}`);
|
|
223
|
+
}
|
|
224
|
+
const text = parsed.content[0]?.text ?? "{}";
|
|
225
|
+
try {
|
|
226
|
+
return JSON.parse(text);
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
throw new Error(`MCP tool ${name} returned non-JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/** Gracefully terminate the MCP process. Idempotent. */
|
|
233
|
+
stop() {
|
|
234
|
+
if (!this.child)
|
|
235
|
+
return;
|
|
236
|
+
if (this.child.exitCode === null) {
|
|
237
|
+
try {
|
|
238
|
+
this.child.kill();
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// already dead — fine
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
this.child = null;
|
|
245
|
+
this.initDone = null;
|
|
246
|
+
this.pending.clear();
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Read framed MCP messages off stdout. Frame = "Content-Length: N\r\n\r\n<body>".
|
|
250
|
+
* Extra leading bytes (mcp server's debug noise on broken setups, etc.) are
|
|
251
|
+
* skipped; we don't expect any here because the server uses stderr for logs.
|
|
252
|
+
*/
|
|
253
|
+
onStdout(chunk) {
|
|
254
|
+
this.buf = Buffer.concat([this.buf, chunk]);
|
|
255
|
+
for (;;) {
|
|
256
|
+
const headerEnd = this.buf.indexOf("\r\n\r\n");
|
|
257
|
+
if (headerEnd === -1)
|
|
258
|
+
return;
|
|
259
|
+
const header = this.buf.subarray(0, headerEnd).toString("ascii");
|
|
260
|
+
const match = /^Content-Length:\s*(\d+)/i.exec(header);
|
|
261
|
+
if (!match) {
|
|
262
|
+
// Protocol violation — drop the bad header and resync.
|
|
263
|
+
// Anything before \r\n\r\n is junk we can't interpret; shift past it.
|
|
264
|
+
this.buf = this.buf.subarray(headerEnd + 4);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const len = Number(match[1]);
|
|
268
|
+
const total = headerEnd + 4 + len;
|
|
269
|
+
if (this.buf.length < total)
|
|
270
|
+
return; // wait for more
|
|
271
|
+
const body = this.buf.subarray(headerEnd + 4, total).toString("utf8");
|
|
272
|
+
this.buf = this.buf.subarray(total);
|
|
273
|
+
this.handleFrame(body);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
handleFrame(body) {
|
|
277
|
+
let parsed;
|
|
278
|
+
try {
|
|
279
|
+
parsed = JSON.parse(body);
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
// Malformed JSON from server — surface but don't crash the whole client.
|
|
283
|
+
printError(`MCP server sent malformed JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const result = JsonRpcResponseSchema.safeParse(parsed);
|
|
287
|
+
if (!result.success)
|
|
288
|
+
return; // not a response (e.g. a server-initiated request we don't expect)
|
|
289
|
+
const handler = this.pending.get(result.data.id);
|
|
290
|
+
if (!handler)
|
|
291
|
+
return;
|
|
292
|
+
this.pending.delete(result.data.id);
|
|
293
|
+
handler.resolve(result.data);
|
|
294
|
+
}
|
|
295
|
+
send(method, params) {
|
|
296
|
+
if (!this.child || !this.child.stdin) {
|
|
297
|
+
return Promise.reject(new Error("MCP client not started"));
|
|
298
|
+
}
|
|
299
|
+
const id = this.nextId++;
|
|
300
|
+
const idStr = String(id);
|
|
301
|
+
const request = JsonRpcRequestSchema.parse({
|
|
302
|
+
jsonrpc: "2.0",
|
|
303
|
+
id: idStr,
|
|
304
|
+
method,
|
|
305
|
+
params,
|
|
306
|
+
});
|
|
307
|
+
return this.writeFrame(request, idStr);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Build a Content-Length framed JSON-RPC request and write it to the
|
|
311
|
+
* child's stdin. Public-and-testable so the wire shape can be unit-tested
|
|
312
|
+
* without spawning the real MCP server.
|
|
313
|
+
*
|
|
314
|
+
* Exposed for tests — not part of the public API callers should depend on.
|
|
315
|
+
*/
|
|
316
|
+
writeFrame(request, idStr) {
|
|
317
|
+
if (!this.child || !this.child.stdin) {
|
|
318
|
+
return Promise.reject(new Error("MCP client not started"));
|
|
319
|
+
}
|
|
320
|
+
const body = JSON.stringify(request);
|
|
321
|
+
const frame = `Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`;
|
|
322
|
+
return new Promise((resolve, reject) => {
|
|
323
|
+
this.pending.set(idStr, { resolve, reject });
|
|
324
|
+
this.child.stdin.write(frame, "utf8", (err) => {
|
|
325
|
+
if (err) {
|
|
326
|
+
this.pending.delete(idStr);
|
|
327
|
+
reject(err);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Fire-and-forget notification (no id, no response). Used for
|
|
334
|
+
* `notifications/initialized` per the MCP spec.
|
|
335
|
+
*/
|
|
336
|
+
notify(method, params) {
|
|
337
|
+
if (!this.child || !this.child.stdin)
|
|
338
|
+
return;
|
|
339
|
+
const body = JSON.stringify({ jsonrpc: "2.0", method, params });
|
|
340
|
+
const frame = `Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`;
|
|
341
|
+
try {
|
|
342
|
+
this.child.stdin.write(frame, "utf8");
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
// best-effort
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
failAllPending(err) {
|
|
349
|
+
for (const [, handler] of this.pending) {
|
|
350
|
+
handler.reject(err);
|
|
351
|
+
}
|
|
352
|
+
this.pending.clear();
|
|
353
|
+
}
|
|
354
|
+
/** Diagnostic: which binary is in use. Useful for `krexel whoami` and tests. */
|
|
355
|
+
describeServer() {
|
|
356
|
+
return this.location?.source ?? "(not started)";
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/** Convenience singleton + auto-stop on process exit. */
|
|
360
|
+
let _client = null;
|
|
361
|
+
export function getClient() {
|
|
362
|
+
if (!_client) {
|
|
363
|
+
_client = new KrexelMcpClient();
|
|
364
|
+
// Make absolutely sure the child process doesn't outlive the CLI.
|
|
365
|
+
const cleanup = () => _client?.stop();
|
|
366
|
+
process.once("exit", cleanup);
|
|
367
|
+
process.once("SIGINT", () => {
|
|
368
|
+
cleanup();
|
|
369
|
+
process.exit(130);
|
|
370
|
+
});
|
|
371
|
+
process.once("SIGTERM", () => {
|
|
372
|
+
cleanup();
|
|
373
|
+
process.exit(143);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
return _client;
|
|
377
|
+
}
|
|
378
|
+
export { nanoid };
|
|
379
|
+
//# sourceMappingURL=mcp-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-client.js","sourceRoot":"","sources":["../src/mcp-client.ts"],"names":[],"mappings":"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"}
|
package/dist/output.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* output.ts — terminal formatting primitives for the Krexel CLI.
|
|
3
|
+
*
|
|
4
|
+
* All user-facing output goes through these helpers so we get one consistent
|
|
5
|
+
* visual language and so `--json` can swap text for a JSON envelope without
|
|
6
|
+
* touching every subcommand.
|
|
7
|
+
*
|
|
8
|
+
* Conventions:
|
|
9
|
+
* - success / warn / error are bold + colored
|
|
10
|
+
* - dim is for side-notes and context
|
|
11
|
+
* - Spinner factory returns a spinner only when the user is expected to
|
|
12
|
+
* wait > 500ms; short ops just print and move on.
|
|
13
|
+
*/
|
|
14
|
+
import chalk from "chalk";
|
|
15
|
+
export declare function setJsonMode(on: boolean): void;
|
|
16
|
+
export declare function isJsonMode(): boolean;
|
|
17
|
+
/** Print success line. */
|
|
18
|
+
export declare function success(msg: string): void;
|
|
19
|
+
/** Print warning line. */
|
|
20
|
+
export declare function warn(msg: string): void;
|
|
21
|
+
/** Print dim/secondary line. */
|
|
22
|
+
export declare function dim(msg: string): void;
|
|
23
|
+
/** Print error line on stderr — exits process with code 1. */
|
|
24
|
+
export declare function error(msg: string): never;
|
|
25
|
+
/** Print error without exiting — useful when caller wants to recover. */
|
|
26
|
+
export declare function printError(msg: string): void;
|
|
27
|
+
/** Bordered key/value line for account status / deploy summary. */
|
|
28
|
+
export declare function kv(key: string, value: string | number | boolean | null | undefined): void;
|
|
29
|
+
/**
|
|
30
|
+
* Run an async operation with a spinner iff the operation is expected to take
|
|
31
|
+
* more than ~500ms. We can't actually measure before we start, so we always
|
|
32
|
+
* show the spinner when called — subcommands decide whether to bother.
|
|
33
|
+
*
|
|
34
|
+
* If --json is set, the spinner is skipped (noise breaks jq pipelines).
|
|
35
|
+
*/
|
|
36
|
+
export declare function withSpinner<T>(text: string, fn: () => Promise<T>, opts?: {
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
}): Promise<T>;
|
|
39
|
+
/**
|
|
40
|
+
* Monospace block if the terminal reports it can render Unicode + color.
|
|
41
|
+
* Falls back to plain text on dumb pipes (so logs still grep cleanly).
|
|
42
|
+
*/
|
|
43
|
+
export declare function monospace(block: string): void;
|
|
44
|
+
export { chalk };
|