@cleocode/runtime 2026.5.130 → 2026.5.132
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/dist/chunk-5ZXRYEQ5.js +190 -0
- package/dist/chunk-5ZXRYEQ5.js.map +1 -0
- package/dist/chunk-R4XBXUQN.js +253 -0
- package/dist/chunk-R4XBXUQN.js.map +1 -0
- package/dist/chunk-WSQQ5DQL.js +215 -0
- package/dist/chunk-WSQQ5DQL.js.map +1 -0
- package/dist/chunk-YZBIPGMH.js +305 -0
- package/dist/chunk-YZBIPGMH.js.map +1 -0
- package/dist/daemon/index.d.ts +306 -0
- package/dist/daemon/index.js +15 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/gateway/http/index.d.ts +10 -0
- package/dist/gateway/http/index.js +21 -0
- package/dist/gateway/http/index.js.map +1 -0
- package/dist/gateway/index.d.ts +10 -0
- package/dist/gateway/index.js +1220 -0
- package/dist/gateway/index.js.map +1 -0
- package/dist/gateway/mcp/index.d.ts +10 -0
- package/dist/gateway/mcp/index.js +21 -0
- package/dist/gateway/mcp/index.js.map +1 -0
- package/dist/gateway/rpc/index.d.ts +1545 -0
- package/dist/gateway/rpc/index.js +17 -0
- package/dist/gateway/rpc/index.js.map +1 -0
- package/dist/index.d.ts +78 -2
- package/dist/index.js +253 -1
- package/dist/index.js.map +1 -1
- package/package.json +24 -3
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// src/gateway/rpc/codec.ts
|
|
2
|
+
import {
|
|
3
|
+
GATEWAY_RPC_PROTOCOL_VERSION,
|
|
4
|
+
GatewayRpcFrameSchema,
|
|
5
|
+
isFrozenGatewayRpcVersion
|
|
6
|
+
} from "@cleocode/contracts/gateway/rpc";
|
|
7
|
+
var NEWLINE = "\n";
|
|
8
|
+
function encodeFrame(frame) {
|
|
9
|
+
return `${JSON.stringify(frame)}${NEWLINE}`;
|
|
10
|
+
}
|
|
11
|
+
function buildErrorFrame(id, code, message) {
|
|
12
|
+
return {
|
|
13
|
+
protocol_version: GATEWAY_RPC_PROTOCOL_VERSION,
|
|
14
|
+
id,
|
|
15
|
+
direction: "error",
|
|
16
|
+
error: { code, message }
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function decodeLine(line) {
|
|
20
|
+
let raw;
|
|
21
|
+
try {
|
|
22
|
+
raw = JSON.parse(line);
|
|
23
|
+
} catch {
|
|
24
|
+
return { kind: "error", frame: buildErrorFrame("0", "E_RPC_PARSE", "invalid JSON line") };
|
|
25
|
+
}
|
|
26
|
+
const recoveredId = recoverId(raw);
|
|
27
|
+
const parsed = GatewayRpcFrameSchema.safeParse(raw);
|
|
28
|
+
if (!parsed.success) {
|
|
29
|
+
return {
|
|
30
|
+
kind: "error",
|
|
31
|
+
frame: buildErrorFrame(recoveredId, "E_RPC_BAD_FRAME", "frame failed schema validation")
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const frame = parsed.data;
|
|
35
|
+
if (!isFrozenGatewayRpcVersion(frame.protocol_version)) {
|
|
36
|
+
return {
|
|
37
|
+
kind: "error",
|
|
38
|
+
frame: buildErrorFrame(
|
|
39
|
+
frame.id,
|
|
40
|
+
"E_RPC_BAD_VERSION",
|
|
41
|
+
`unsupported protocol version ${frame.protocol_version}; server speaks ${GATEWAY_RPC_PROTOCOL_VERSION}`
|
|
42
|
+
)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (frame.direction !== "request") {
|
|
46
|
+
return {
|
|
47
|
+
kind: "error",
|
|
48
|
+
frame: buildErrorFrame(
|
|
49
|
+
frame.id,
|
|
50
|
+
"E_RPC_BAD_FRAME",
|
|
51
|
+
`server cannot accept a ${frame.direction} frame`
|
|
52
|
+
)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return { kind: "request", frame };
|
|
56
|
+
}
|
|
57
|
+
function recoverId(raw) {
|
|
58
|
+
if (raw && typeof raw === "object" && "id" in raw) {
|
|
59
|
+
const id = raw.id;
|
|
60
|
+
if (typeof id === "string" && id.length > 0) return id;
|
|
61
|
+
}
|
|
62
|
+
return "0";
|
|
63
|
+
}
|
|
64
|
+
var LineBuffer = class {
|
|
65
|
+
/** Pending bytes that have not yet completed a line. */
|
|
66
|
+
buffer = "";
|
|
67
|
+
/**
|
|
68
|
+
* Push a decoded UTF-8 chunk; return every complete line it completes.
|
|
69
|
+
*
|
|
70
|
+
* @param chunk - A UTF-8 string chunk from the socket.
|
|
71
|
+
* @returns Zero or more complete lines (trailing newline stripped, blanks
|
|
72
|
+
* skipped).
|
|
73
|
+
*/
|
|
74
|
+
push(chunk) {
|
|
75
|
+
this.buffer += chunk;
|
|
76
|
+
const lines = [];
|
|
77
|
+
let idx = this.buffer.indexOf(NEWLINE);
|
|
78
|
+
while (idx !== -1) {
|
|
79
|
+
const line = this.buffer.slice(0, idx).trim();
|
|
80
|
+
this.buffer = this.buffer.slice(idx + 1);
|
|
81
|
+
if (line.length > 0) lines.push(line);
|
|
82
|
+
idx = this.buffer.indexOf(NEWLINE);
|
|
83
|
+
}
|
|
84
|
+
return lines;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/gateway/rpc/server.ts
|
|
89
|
+
import { existsSync, unlinkSync } from "fs";
|
|
90
|
+
import { createServer } from "net";
|
|
91
|
+
import { GATEWAY_RPC_PROTOCOL_VERSION as GATEWAY_RPC_PROTOCOL_VERSION2 } from "@cleocode/contracts/gateway/rpc";
|
|
92
|
+
import { getLogger } from "@cleocode/core";
|
|
93
|
+
async function routeFrame(handler, frame) {
|
|
94
|
+
const request = {
|
|
95
|
+
...frame.request,
|
|
96
|
+
// Force the transport of origin — a client cannot impersonate cli/mcp/http.
|
|
97
|
+
source: "rpc",
|
|
98
|
+
requestId: frame.request.requestId
|
|
99
|
+
};
|
|
100
|
+
try {
|
|
101
|
+
const response = await handler.handle(request);
|
|
102
|
+
return {
|
|
103
|
+
protocol_version: GATEWAY_RPC_PROTOCOL_VERSION2,
|
|
104
|
+
id: frame.id,
|
|
105
|
+
direction: "response",
|
|
106
|
+
response
|
|
107
|
+
};
|
|
108
|
+
} catch (err) {
|
|
109
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
110
|
+
return buildErrorFrame(frame.id, "E_RPC_INTERNAL", message);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function handleConnection(handler, socket, log) {
|
|
114
|
+
socket.setEncoding("utf8");
|
|
115
|
+
const lines = new LineBuffer();
|
|
116
|
+
socket.on("data", (chunk) => {
|
|
117
|
+
for (const line of lines.push(chunk)) {
|
|
118
|
+
const decoded = decodeLine(line);
|
|
119
|
+
if (decoded.kind === "error") {
|
|
120
|
+
socket.write(encodeFrame(decoded.frame));
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
routeFrame(handler, decoded.frame).then((out) => {
|
|
124
|
+
if (!socket.destroyed) socket.write(encodeFrame(out));
|
|
125
|
+
}).catch((err) => {
|
|
126
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
127
|
+
log.warn({ err }, "rpc frame routing failed");
|
|
128
|
+
if (!socket.destroyed) {
|
|
129
|
+
socket.write(encodeFrame(buildErrorFrame(decoded.frame.id, "E_RPC_INTERNAL", message)));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
socket.on("error", (err) => {
|
|
135
|
+
log.warn({ err }, "rpc connection error");
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function startRpcServer(handler, opts) {
|
|
139
|
+
const log = getLogger("gateway-rpc");
|
|
140
|
+
const removeStale = opts.removeStaleSocket ?? true;
|
|
141
|
+
if (removeStale && existsSync(opts.socketPath)) {
|
|
142
|
+
try {
|
|
143
|
+
unlinkSync(opts.socketPath);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
log.warn({ err, socketPath: opts.socketPath }, "failed to unlink stale rpc socket");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const server = createServer((socket) => handleConnection(handler, socket, log));
|
|
149
|
+
return new Promise((resolve, reject) => {
|
|
150
|
+
server.once("error", reject);
|
|
151
|
+
server.listen(opts.socketPath, () => {
|
|
152
|
+
server.removeListener("error", reject);
|
|
153
|
+
log.info(
|
|
154
|
+
{ socketPath: opts.socketPath, version: GATEWAY_RPC_PROTOCOL_VERSION2 },
|
|
155
|
+
"rpc server listening"
|
|
156
|
+
);
|
|
157
|
+
let closed = false;
|
|
158
|
+
const handle = {
|
|
159
|
+
server,
|
|
160
|
+
socketPath: opts.socketPath,
|
|
161
|
+
close() {
|
|
162
|
+
if (closed) return Promise.resolve();
|
|
163
|
+
closed = true;
|
|
164
|
+
return new Promise((res) => {
|
|
165
|
+
server.close(() => {
|
|
166
|
+
if (existsSync(opts.socketPath)) {
|
|
167
|
+
try {
|
|
168
|
+
unlinkSync(opts.socketPath);
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
res();
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
resolve(handle);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export {
|
|
183
|
+
encodeFrame,
|
|
184
|
+
buildErrorFrame,
|
|
185
|
+
decodeLine,
|
|
186
|
+
LineBuffer,
|
|
187
|
+
routeFrame,
|
|
188
|
+
startRpcServer
|
|
189
|
+
};
|
|
190
|
+
//# sourceMappingURL=chunk-5ZXRYEQ5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/gateway/rpc/codec.ts","../src/gateway/rpc/server.ts"],"sourcesContent":["/**\n * NDJSON codec for the CLI-RPC transport.\n *\n * Implements the SAME framing discipline as the FROZEN `supervisor-ipc` v1.0\n * contract — one zod-validated JSON envelope per newline-delimited line, with\n * an explicit `protocol_version` negotiation step — but for the gateway-RPC\n * frame set (`@cleocode/contracts/gateway/rpc`). The supervisor-ipc module is\n * frozen and is NOT edited; this codec re-applies its pattern.\n *\n * The codec is intentionally socket-agnostic: it owns line buffering, JSON\n * parse, zod decode, and version negotiation, returning typed results the\n * server maps onto `node:net` reads/writes. This keeps the parse/validate logic\n * unit-testable without binding a real socket.\n *\n * @task T11449\n * @epic T11254\n * @saga T11243\n */\n\nimport {\n GATEWAY_RPC_PROTOCOL_VERSION,\n type GatewayRpcErrorCode,\n type GatewayRpcErrorFrame,\n type GatewayRpcFrame,\n GatewayRpcFrameSchema,\n type GatewayRpcRequestFrame,\n type GatewayRpcResponseFrame,\n isFrozenGatewayRpcVersion,\n} from '@cleocode/contracts/gateway/rpc';\n\n/** Newline delimiter for the NDJSON wire (mirrors supervisor-ipc). */\nconst NEWLINE = '\\n';\n\n/**\n * Encode any RPC frame to a single NDJSON line (JSON + trailing `\\n`).\n *\n * @param frame - The response or error frame to serialize.\n * @returns A newline-terminated JSON string ready to write to the socket.\n */\nexport function encodeFrame(frame: GatewayRpcResponseFrame | GatewayRpcErrorFrame): string {\n return `${JSON.stringify(frame)}${NEWLINE}`;\n}\n\n/**\n * Build a protocol-level error frame.\n *\n * @param id - Correlation id (`'0'` when the originating frame's id is unknown).\n * @param code - One of the frozen {@link GatewayRpcErrorCode} values.\n * @param message - Human-readable detail.\n * @returns A fully-formed {@link GatewayRpcErrorFrame}.\n */\nexport function buildErrorFrame(\n id: string,\n code: GatewayRpcErrorCode,\n message: string,\n): GatewayRpcErrorFrame {\n return {\n protocol_version: GATEWAY_RPC_PROTOCOL_VERSION,\n id,\n direction: 'error',\n error: { code, message },\n };\n}\n\n/**\n * The discriminated outcome of decoding a single inbound NDJSON line.\n *\n * - `request` — a valid, version-matched request frame ready to route.\n * - `error` — a protocol-level error frame the server should write straight\n * back (parse failure, version mismatch, or malformed frame). The\n * server never routes these to the gateway.\n */\nexport type DecodeResult =\n | { kind: 'request'; frame: GatewayRpcRequestFrame }\n | { kind: 'error'; frame: GatewayRpcErrorFrame };\n\n/**\n * Decode + validate a single inbound NDJSON line into a routable request or a\n * protocol-level error frame.\n *\n * Steps (the supervisor-ipc pattern, applied to gateway frames):\n * 1. JSON parse — failure → `E_RPC_PARSE`.\n * 2. zod decode against {@link GatewayRpcFrameSchema} — failure → `E_RPC_BAD_FRAME`.\n * 3. version negotiation via {@link isFrozenGatewayRpcVersion} —\n * mismatch → `E_RPC_BAD_VERSION`.\n * 4. direction must be `request` (a server never receives response/error\n * frames) — otherwise → `E_RPC_BAD_FRAME`.\n *\n * The best-effort correlation `id` is recovered from the raw JSON when possible\n * so the client can match the error to its request even on a decode failure.\n *\n * @param line - One trimmed NDJSON line (no trailing newline).\n * @returns A {@link DecodeResult}.\n */\nexport function decodeLine(line: string): DecodeResult {\n let raw: unknown;\n try {\n raw = JSON.parse(line);\n } catch {\n return { kind: 'error', frame: buildErrorFrame('0', 'E_RPC_PARSE', 'invalid JSON line') };\n }\n\n // Best-effort id recovery for error correlation before full validation.\n const recoveredId = recoverId(raw);\n\n const parsed = GatewayRpcFrameSchema.safeParse(raw);\n if (!parsed.success) {\n return {\n kind: 'error',\n frame: buildErrorFrame(recoveredId, 'E_RPC_BAD_FRAME', 'frame failed schema validation'),\n };\n }\n\n const frame: GatewayRpcFrame = parsed.data;\n\n if (!isFrozenGatewayRpcVersion(frame.protocol_version)) {\n return {\n kind: 'error',\n frame: buildErrorFrame(\n frame.id,\n 'E_RPC_BAD_VERSION',\n `unsupported protocol version ${frame.protocol_version}; server speaks ${GATEWAY_RPC_PROTOCOL_VERSION}`,\n ),\n };\n }\n\n if (frame.direction !== 'request') {\n return {\n kind: 'error',\n frame: buildErrorFrame(\n frame.id,\n 'E_RPC_BAD_FRAME',\n `server cannot accept a ${frame.direction} frame`,\n ),\n };\n }\n\n return { kind: 'request', frame };\n}\n\n/**\n * Recover a best-effort correlation id from raw decoded JSON, used so a\n * validation failure can still be correlated to the client's request.\n *\n * @param raw - The result of `JSON.parse` on the inbound line.\n * @returns The string `id` if present and non-empty, else `'0'`.\n */\nfunction recoverId(raw: unknown): string {\n if (raw && typeof raw === 'object' && 'id' in raw) {\n const id = (raw as { id: unknown }).id;\n if (typeof id === 'string' && id.length > 0) return id;\n }\n return '0';\n}\n\n/**\n * A streaming line splitter for the NDJSON wire.\n *\n * `node:net` sockets deliver arbitrary chunk boundaries; a single `data` event\n * may carry a partial line or several lines. This buffer accumulates bytes and\n * yields only complete lines (split on `\\n`), matching the supervisor-ipc\n * client's `readline`-style framing without depending on `readline` (so it can\n * be driven directly off socket chunks).\n */\nexport class LineBuffer {\n /** Pending bytes that have not yet completed a line. */\n private buffer = '';\n\n /**\n * Push a decoded UTF-8 chunk; return every complete line it completes.\n *\n * @param chunk - A UTF-8 string chunk from the socket.\n * @returns Zero or more complete lines (trailing newline stripped, blanks\n * skipped).\n */\n push(chunk: string): string[] {\n this.buffer += chunk;\n const lines: string[] = [];\n let idx = this.buffer.indexOf(NEWLINE);\n while (idx !== -1) {\n const line = this.buffer.slice(0, idx).trim();\n this.buffer = this.buffer.slice(idx + 1);\n if (line.length > 0) lines.push(line);\n idx = this.buffer.indexOf(NEWLINE);\n }\n return lines;\n }\n}\n","/**\n * CLI-RPC unix-socket transport adapter — routes NDJSON frames through the gateway.\n *\n * Serves the gateway over a long-lived unix-domain socket using newline-delimited\n * JSON (NDJSON): one zod-validated {@link GatewayRpcFrame} per line. Any local\n * client (the `cleo` CLI talking to a warm daemon, Studio's server process, a\n * test harness) can open the socket, write `request` frames, and read back\n * `response`/`error` frames — without paying the per-invocation cold-start of\n * spawning a fresh `cleo` process.\n *\n * This is the THIRD transport adapter, mirroring the just-merged MCP stdio\n * adapter (`packages/runtime/src/gateway/mcp/`):\n *\n * RPC request frame { direction:'request', request:{…} }\n * → decode + validate (`@cleocode/contracts/gateway/rpc`)\n * → DispatchRequest { source: 'rpc', … } (re-validated at the boundary)\n * → injected {@link GatewayHandler}.handle()\n * → DispatchResponse (LAFS envelope) → RPC response frame\n *\n * The adapter owns ONLY wire concerns: socket lifecycle, line framing, version\n * negotiation, error rendering, and per-connection cleanup. All domain logic\n * (resolution, validation, middleware, handler execution) lives behind the\n * {@link GatewayHandler} — exactly mirroring the CLI and MCP adapters. There is\n * NO `process.exit` and NO error-render inside the handlers (R3 contract); the\n * adapter never calls `process.exit` at all (lifecycle is the caller's).\n *\n * The request `source` is forced to `'rpc'` regardless of what the client sent,\n * so a client cannot impersonate another transport.\n *\n * @task T11449\n * @epic T11254\n * @saga T11243\n */\n\nimport { existsSync, unlinkSync } from 'node:fs';\nimport { createServer, type Socket } from 'node:net';\nimport type { DispatchRequest } from '@cleocode/contracts/gateway';\nimport type {\n GatewayRpcRequestFrame,\n GatewayRpcResponseFrame,\n} from '@cleocode/contracts/gateway/rpc';\nimport { GATEWAY_RPC_PROTOCOL_VERSION } from '@cleocode/contracts/gateway/rpc';\nimport { getLogger } from '@cleocode/core';\nimport type { GatewayHandler } from '../index.js';\nimport { buildErrorFrame, decodeLine, encodeFrame, LineBuffer } from './codec.js';\nimport type { RpcServerHandle, RpcServerOptions } from './types.js';\n\n/**\n * Route a single decoded request frame through the gateway handler and build\n * the correlated response frame.\n *\n * The request's `source` is forced to `'rpc'`; the frame `id` is reused as the\n * response correlation id (and, when the client omitted a `requestId` shaped\n * for tracing, the frame id flows through as the dispatch `requestId`). Thrown\n * handler errors are caught and rendered as a protocol-level `E_RPC_INTERNAL`\n * error frame — never propagated as an unhandled rejection or `process.exit`.\n *\n * @param handler - The injected transport-neutral gateway handler.\n * @param frame - A validated, version-matched request frame.\n * @returns The response or error frame to write back on the same connection.\n */\nexport async function routeFrame(\n handler: GatewayHandler,\n frame: GatewayRpcRequestFrame,\n): Promise<GatewayRpcResponseFrame | ReturnType<typeof buildErrorFrame>> {\n const request: DispatchRequest = {\n ...frame.request,\n // Force the transport of origin — a client cannot impersonate cli/mcp/http.\n source: 'rpc',\n requestId: frame.request.requestId,\n };\n\n try {\n const response = await handler.handle(request);\n return {\n protocol_version: GATEWAY_RPC_PROTOCOL_VERSION,\n id: frame.id,\n direction: 'response',\n response,\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return buildErrorFrame(frame.id, 'E_RPC_INTERNAL', message);\n }\n}\n\n/**\n * Handle a single client connection: buffer NDJSON lines, decode each, route\n * request frames through the gateway, and write back response/error frames.\n *\n * Each connection owns its own {@link LineBuffer}, so partial-line chunk\n * boundaries are reassembled per socket. Frames are processed concurrently;\n * each correlated response carries the originating frame `id`, so the client\n * can demultiplex out-of-order completions.\n *\n * @param handler - The injected gateway handler.\n * @param socket - The accepted `node:net` connection.\n * @param log - The adapter's pino logger.\n */\nfunction handleConnection(\n handler: GatewayHandler,\n socket: Socket,\n log: ReturnType<typeof getLogger>,\n): void {\n socket.setEncoding('utf8');\n const lines = new LineBuffer();\n\n socket.on('data', (chunk: string) => {\n for (const line of lines.push(chunk)) {\n const decoded = decodeLine(line);\n if (decoded.kind === 'error') {\n socket.write(encodeFrame(decoded.frame));\n continue;\n }\n routeFrame(handler, decoded.frame)\n .then((out) => {\n if (!socket.destroyed) socket.write(encodeFrame(out));\n })\n .catch((err: unknown) => {\n // routeFrame already traps handler errors; this guards the framing path.\n const message = err instanceof Error ? err.message : String(err);\n log.warn({ err }, 'rpc frame routing failed');\n if (!socket.destroyed) {\n socket.write(encodeFrame(buildErrorFrame(decoded.frame.id, 'E_RPC_INTERNAL', message)));\n }\n });\n }\n });\n\n socket.on('error', (err) => {\n log.warn({ err }, 'rpc connection error');\n });\n}\n\n/**\n * Start the CLI-RPC unix-socket server over the gateway.\n *\n * Binds {@link RpcServerOptions.socketPath}, accepts connections, and routes\n * each inbound NDJSON `request` frame through the injected {@link GatewayHandler}.\n * The caller assembles the handler (via `createGatewayHandler` with its domain\n * handlers + middleware) and injects it here — this adapter never builds\n * handlers itself, keeping the runtime free of any `@cleocode/cleo` dependency.\n *\n * Returns once the socket is listening. The returned {@link RpcServerHandle}\n * exposes an idempotent `close()` for deterministic teardown (daemon shutdown,\n * tests). The adapter never calls `process.exit`.\n *\n * @param handler - The transport-neutral gateway handler to route through.\n * @param opts - Socket path + lifecycle options.\n * @returns A promise resolving to the live {@link RpcServerHandle}.\n */\nexport function startRpcServer(\n handler: GatewayHandler,\n opts: RpcServerOptions,\n): Promise<RpcServerHandle> {\n const log = getLogger('gateway-rpc');\n const removeStale = opts.removeStaleSocket ?? true;\n\n if (removeStale && existsSync(opts.socketPath)) {\n try {\n unlinkSync(opts.socketPath);\n } catch (err) {\n log.warn({ err, socketPath: opts.socketPath }, 'failed to unlink stale rpc socket');\n }\n }\n\n const server = createServer((socket) => handleConnection(handler, socket, log));\n\n return new Promise<RpcServerHandle>((resolve, reject) => {\n server.once('error', reject);\n server.listen(opts.socketPath, () => {\n server.removeListener('error', reject);\n log.info(\n { socketPath: opts.socketPath, version: GATEWAY_RPC_PROTOCOL_VERSION },\n 'rpc server listening',\n );\n\n let closed = false;\n const handle: RpcServerHandle = {\n server,\n socketPath: opts.socketPath,\n close(): Promise<void> {\n if (closed) return Promise.resolve();\n closed = true;\n return new Promise<void>((res) => {\n server.close(() => {\n if (existsSync(opts.socketPath)) {\n try {\n unlinkSync(opts.socketPath);\n } catch {\n // best-effort cleanup; the OS reclaims the node on exit anyway.\n }\n }\n res();\n });\n });\n },\n };\n resolve(handle);\n });\n });\n}\n"],"mappings":"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","names":["GATEWAY_RPC_PROTOCOL_VERSION","GATEWAY_RPC_PROTOCOL_VERSION"]}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// src/gateway/mcp/server.ts
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import * as readline from "readline";
|
|
4
|
+
import { getLogger } from "@cleocode/core";
|
|
5
|
+
|
|
6
|
+
// src/gateway/mcp/tool-naming.ts
|
|
7
|
+
var MCP_TOOL_PREFIX = "cleo_";
|
|
8
|
+
function toSnake(segment) {
|
|
9
|
+
return segment.replace(/[.-]/g, "_");
|
|
10
|
+
}
|
|
11
|
+
function operationToToolName(op) {
|
|
12
|
+
return `${MCP_TOOL_PREFIX}${toSnake(op.domain)}_${toSnake(op.operation)}`;
|
|
13
|
+
}
|
|
14
|
+
function toolNameToOperationKey(toolName, exposed) {
|
|
15
|
+
const match = exposed.find((op) => operationToToolName(op) === toolName);
|
|
16
|
+
return match ? { domain: match.domain, operation: match.operation } : void 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/gateway/registry.ts
|
|
20
|
+
import { OPERATIONS as _OPERATIONS } from "@cleocode/contracts";
|
|
21
|
+
var OPERATIONS = _OPERATIONS;
|
|
22
|
+
function deriveGatewayMatrix(gateway) {
|
|
23
|
+
const matrix = {};
|
|
24
|
+
for (const op of OPERATIONS) {
|
|
25
|
+
if (op.gateway !== gateway) continue;
|
|
26
|
+
if (!matrix[op.domain]) matrix[op.domain] = [];
|
|
27
|
+
matrix[op.domain].push(op.operation);
|
|
28
|
+
}
|
|
29
|
+
return matrix;
|
|
30
|
+
}
|
|
31
|
+
function getGatewayDomains(gateway) {
|
|
32
|
+
return Object.keys(deriveGatewayMatrix(gateway));
|
|
33
|
+
}
|
|
34
|
+
function resolve(gateway, domain, operation) {
|
|
35
|
+
const def = OPERATIONS.find(
|
|
36
|
+
(o) => o.gateway === gateway && o.domain === domain && o.operation === operation
|
|
37
|
+
);
|
|
38
|
+
if (!def) return void 0;
|
|
39
|
+
return { domain: def.domain, operation: def.operation, def };
|
|
40
|
+
}
|
|
41
|
+
function validateRequiredParams(def, params) {
|
|
42
|
+
if (!def.requiredParams || def.requiredParams.length === 0) return [];
|
|
43
|
+
const provided = params || {};
|
|
44
|
+
const allowEmptyKeys = /* @__PURE__ */ new Set();
|
|
45
|
+
if (def.params) {
|
|
46
|
+
for (const p of def.params) {
|
|
47
|
+
if (p.allowEmpty === true) allowEmptyKeys.add(p.name);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return def.requiredParams.filter((key) => {
|
|
51
|
+
const v = provided[key];
|
|
52
|
+
if (v === void 0 || v === null) return true;
|
|
53
|
+
if (v === "" && !allowEmptyKeys.has(key)) return true;
|
|
54
|
+
return false;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function getByDomain(domain) {
|
|
58
|
+
return OPERATIONS.filter((o) => o.domain === domain);
|
|
59
|
+
}
|
|
60
|
+
function getByGateway(gateway) {
|
|
61
|
+
return OPERATIONS.filter((o) => o.gateway === gateway);
|
|
62
|
+
}
|
|
63
|
+
function getByTier(tier) {
|
|
64
|
+
return OPERATIONS.filter((o) => o.tier <= tier);
|
|
65
|
+
}
|
|
66
|
+
function getActiveDomains() {
|
|
67
|
+
const active = new Set(OPERATIONS.map((o) => o.domain));
|
|
68
|
+
return Array.from(active);
|
|
69
|
+
}
|
|
70
|
+
function getCounts() {
|
|
71
|
+
return {
|
|
72
|
+
query: OPERATIONS.filter((o) => o.gateway === "query").length,
|
|
73
|
+
mutate: OPERATIONS.filter((o) => o.gateway === "mutate").length,
|
|
74
|
+
total: OPERATIONS.length
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
var counts = getCounts();
|
|
78
|
+
if (counts.total !== OPERATIONS.length) {
|
|
79
|
+
console.warn(
|
|
80
|
+
`[Registry] Operation count mismatch: total=${counts.total}, registry=${OPERATIONS.length}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/gateway/mcp/tools-list.ts
|
|
85
|
+
function exposedOperations() {
|
|
86
|
+
return OPERATIONS.filter((op) => op.mcpExposed === true);
|
|
87
|
+
}
|
|
88
|
+
function paramTypeToJsonSchema(type) {
|
|
89
|
+
return type === "array" ? "string" : type;
|
|
90
|
+
}
|
|
91
|
+
function buildInputSchema(op) {
|
|
92
|
+
const properties = {};
|
|
93
|
+
const required = [];
|
|
94
|
+
for (const param of op.params ?? []) {
|
|
95
|
+
if (param.hidden === true) continue;
|
|
96
|
+
const prop = {
|
|
97
|
+
type: paramTypeToJsonSchema(param.type),
|
|
98
|
+
description: param.description
|
|
99
|
+
};
|
|
100
|
+
if (param.enum !== void 0 && param.enum.length > 0) {
|
|
101
|
+
prop.enum = param.enum;
|
|
102
|
+
}
|
|
103
|
+
properties[param.name] = prop;
|
|
104
|
+
if (param.required) required.push(param.name);
|
|
105
|
+
}
|
|
106
|
+
const schema = { type: "object", properties };
|
|
107
|
+
if (required.length > 0) schema.required = required;
|
|
108
|
+
return schema;
|
|
109
|
+
}
|
|
110
|
+
function operationToMcpTool(op) {
|
|
111
|
+
return {
|
|
112
|
+
name: operationToToolName(op),
|
|
113
|
+
description: op.description,
|
|
114
|
+
inputSchema: buildInputSchema(op)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function buildToolsList() {
|
|
118
|
+
return exposedOperations().map(operationToMcpTool);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/gateway/mcp/server.ts
|
|
122
|
+
function ok(id, result) {
|
|
123
|
+
return { jsonrpc: "2.0", id, result };
|
|
124
|
+
}
|
|
125
|
+
function rpcError(id, code, message) {
|
|
126
|
+
return { jsonrpc: "2.0", id, error: { code, message } };
|
|
127
|
+
}
|
|
128
|
+
var SERVER_NAME = "cleo-gateway-mcp";
|
|
129
|
+
var PROTOCOL_VERSION = "2024-11-05";
|
|
130
|
+
var GATEWAY_MCP_VERSION = "1.0.0";
|
|
131
|
+
function responseToToolResult(response) {
|
|
132
|
+
return {
|
|
133
|
+
content: [{ type: "text", text: JSON.stringify(response) }],
|
|
134
|
+
isError: response.success ? void 0 : true
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async function callTool(handler, toolName, args) {
|
|
138
|
+
const exposed = exposedOperations();
|
|
139
|
+
const key = toolNameToOperationKey(toolName, exposed);
|
|
140
|
+
if (!key) {
|
|
141
|
+
return {
|
|
142
|
+
content: [{ type: "text", text: `Unknown tool: ${toolName}` }],
|
|
143
|
+
isError: true
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const def = exposed.find((op) => op.domain === key.domain && op.operation === key.operation);
|
|
147
|
+
if (!def) {
|
|
148
|
+
return {
|
|
149
|
+
content: [{ type: "text", text: `Unknown tool: ${toolName}` }],
|
|
150
|
+
isError: true
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const request = {
|
|
154
|
+
gateway: def.gateway,
|
|
155
|
+
domain: key.domain,
|
|
156
|
+
operation: key.operation,
|
|
157
|
+
params: args,
|
|
158
|
+
source: "mcp",
|
|
159
|
+
requestId: randomUUID()
|
|
160
|
+
};
|
|
161
|
+
try {
|
|
162
|
+
const response = await handler.handle(request);
|
|
163
|
+
return responseToToolResult(response);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
166
|
+
return {
|
|
167
|
+
content: [{ type: "text", text: `Tool execution failed: ${message}` }],
|
|
168
|
+
isError: true
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async function handleRequest(handler, req) {
|
|
173
|
+
const { id, method, params } = req;
|
|
174
|
+
switch (method) {
|
|
175
|
+
case "initialize":
|
|
176
|
+
return ok(id, {
|
|
177
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
178
|
+
capabilities: { tools: {} },
|
|
179
|
+
serverInfo: { name: SERVER_NAME, version: GATEWAY_MCP_VERSION }
|
|
180
|
+
});
|
|
181
|
+
case "tools/list":
|
|
182
|
+
return ok(id, { tools: buildToolsList() });
|
|
183
|
+
case "tools/call": {
|
|
184
|
+
const p = params;
|
|
185
|
+
const toolName = p?.name;
|
|
186
|
+
if (!toolName) {
|
|
187
|
+
return rpcError(id, -32602, "Missing tool name in params.name");
|
|
188
|
+
}
|
|
189
|
+
const result = await callTool(handler, toolName, p?.arguments ?? {});
|
|
190
|
+
return ok(id, result);
|
|
191
|
+
}
|
|
192
|
+
case "notifications/initialized":
|
|
193
|
+
case "ping":
|
|
194
|
+
return ok(id, {});
|
|
195
|
+
default:
|
|
196
|
+
return rpcError(id, -32601, `Method not found: ${method}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function startMcpServer(handler, opts) {
|
|
200
|
+
const input = opts?.input ?? process.stdin;
|
|
201
|
+
const output = opts?.output ?? process.stdout;
|
|
202
|
+
const exitOnClose = opts?.exitOnClose ?? true;
|
|
203
|
+
const log = getLogger("gateway-mcp");
|
|
204
|
+
const rl = readline.createInterface({ input, terminal: false });
|
|
205
|
+
rl.on("line", (line) => {
|
|
206
|
+
const trimmed = line.trim();
|
|
207
|
+
if (!trimmed) return;
|
|
208
|
+
let req;
|
|
209
|
+
try {
|
|
210
|
+
req = JSON.parse(trimmed);
|
|
211
|
+
} catch {
|
|
212
|
+
output.write(`${JSON.stringify(rpcError(null, -32700, "Parse error: invalid JSON"))}
|
|
213
|
+
`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
handleRequest(handler, req).then((response) => {
|
|
217
|
+
output.write(`${JSON.stringify(response)}
|
|
218
|
+
`);
|
|
219
|
+
}).catch((err) => {
|
|
220
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
221
|
+
log.warn({ err }, "mcp request handling failed");
|
|
222
|
+
output.write(
|
|
223
|
+
`${JSON.stringify(rpcError(req.id ?? null, -32603, `Internal error: ${message}`))}
|
|
224
|
+
`
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
rl.on("close", () => {
|
|
229
|
+
if (exitOnClose) process.exit(0);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export {
|
|
234
|
+
OPERATIONS,
|
|
235
|
+
deriveGatewayMatrix,
|
|
236
|
+
getGatewayDomains,
|
|
237
|
+
resolve,
|
|
238
|
+
validateRequiredParams,
|
|
239
|
+
getByDomain,
|
|
240
|
+
getByGateway,
|
|
241
|
+
getByTier,
|
|
242
|
+
getActiveDomains,
|
|
243
|
+
getCounts,
|
|
244
|
+
MCP_TOOL_PREFIX,
|
|
245
|
+
operationToToolName,
|
|
246
|
+
toolNameToOperationKey,
|
|
247
|
+
exposedOperations,
|
|
248
|
+
operationToMcpTool,
|
|
249
|
+
buildToolsList,
|
|
250
|
+
callTool,
|
|
251
|
+
startMcpServer
|
|
252
|
+
};
|
|
253
|
+
//# sourceMappingURL=chunk-R4XBXUQN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/gateway/mcp/server.ts","../src/gateway/mcp/tool-naming.ts","../src/gateway/registry.ts","../src/gateway/mcp/tools-list.ts"],"sourcesContent":["/**\n * MCP stdio transport adapter — routes `tools/call` through the gateway.\n *\n * Serves the Model Context Protocol over stdio (newline-delimited JSON-RPC 2.0)\n * so any MCP-compatible client (Claude Code, LLM-agnostic tools) can discover\n * and invoke CLEO operations. Unlike the legacy standalone `@cleocode/mcp-adapter`\n * — which called the `@cleocode/core` SDK directly — this adapter is a thin\n * TRANSPORT over the unified gateway:\n *\n * MCP `tools/call` { name, arguments }\n * → {@link GatewayOperationKey} (via the `mcpExposed` registry subset)\n * → DispatchRequest { source: 'mcp', gateway, domain, operation, params }\n * → injected {@link GatewayHandler}.handle()\n * → DispatchResponse (LAFS envelope) → MCP tool result (JSON text content)\n *\n * The adapter owns ONLY wire concerns: JSON-RPC framing, the stdio read loop,\n * `tools/list` generation, error rendering, and `process.exit` on stream close.\n * All domain logic (resolution, validation, middleware, handler execution) lives\n * behind the {@link GatewayHandler} — exactly mirroring how the CLI adapter\n * wraps the same handler. `process.exit` and error-render stay HERE, never in\n * the handlers (R3-T4 contract).\n *\n * `tools/list` is generated from the OPERATIONS registry behind the default-deny\n * `mcpExposed` flag, so the external tool surface is identical to the historical\n * standalone adapter (no behavior change).\n *\n * @task T11448\n * @epic T11254\n * @saga T11243\n */\n\nimport { randomUUID } from 'node:crypto';\nimport * as readline from 'node:readline';\nimport type { DispatchRequest, DispatchResponse } from '@cleocode/contracts/gateway';\nimport { getLogger } from '@cleocode/core';\nimport type { GatewayHandler } from '../index.js';\nimport { toolNameToOperationKey } from './tool-naming.js';\nimport { buildToolsList, exposedOperations } from './tools-list.js';\nimport type { McpTool, McpToolResult } from './types.js';\n\n// ---------------------------------------------------------------------------\n// JSON-RPC 2.0 envelope helpers\n// ---------------------------------------------------------------------------\n\n/** Inbound JSON-RPC request frame. */\ninterface JsonRpcRequest {\n jsonrpc: '2.0';\n id: number | string | null;\n method: string;\n params?: unknown;\n}\n\n/** Outbound JSON-RPC response frame. */\ninterface JsonRpcResponse {\n jsonrpc: '2.0';\n id: number | string | null;\n result?: unknown;\n error?: { code: number; message: string; data?: unknown };\n}\n\n/** Build a JSON-RPC success response. */\nfunction ok(id: number | string | null, result: unknown): JsonRpcResponse {\n return { jsonrpc: '2.0', id, result };\n}\n\n/** Build a JSON-RPC error response. */\nfunction rpcError(id: number | string | null, code: number, message: string): JsonRpcResponse {\n return { jsonrpc: '2.0', id, error: { code, message } };\n}\n\n// ---------------------------------------------------------------------------\n// Server identity\n// ---------------------------------------------------------------------------\n\n/** Advertised MCP server name. */\nconst SERVER_NAME = 'cleo-gateway-mcp';\n/** MCP protocol revision the adapter speaks. */\nconst PROTOCOL_VERSION = '2024-11-05';\n/** Version string advertised in the `initialize` handshake. */\nconst GATEWAY_MCP_VERSION = '1.0.0';\n\n// ---------------------------------------------------------------------------\n// tools/call → gateway routing\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap a {@link DispatchResponse} envelope into an MCP tool result.\n *\n * The full LAFS envelope is serialized as the text content so MCP clients see\n * the same `{ success, data, meta, error }` payload every other transport\n * returns. `isError` is set from the envelope's `success` flag.\n *\n * @param response - The gateway dispatch response.\n * @returns An MCP {@link McpToolResult}.\n */\nfunction responseToToolResult(response: DispatchResponse): McpToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(response) }],\n isError: response.success ? undefined : true,\n };\n}\n\n/**\n * Route a single MCP `tools/call` through the gateway handler.\n *\n * Resolves the tool name to a `(domain, operation)` key against the exposed\n * (`mcpExposed`) registry subset, builds a `source: 'mcp'` {@link DispatchRequest},\n * and delegates to the injected {@link GatewayHandler}. Unknown tool names and\n * thrown handler errors are rendered as MCP error results — never propagated as\n * `process.exit` from this layer.\n *\n * @param handler - The injected transport-neutral gateway handler.\n * @param toolName - The MCP `tools/call` tool name.\n * @param args - The parsed tool arguments (already JSON-decoded by the client).\n * @returns An MCP {@link McpToolResult}.\n */\nexport async function callTool(\n handler: GatewayHandler,\n toolName: string,\n args: Record<string, unknown>,\n): Promise<McpToolResult> {\n const exposed = exposedOperations();\n const key = toolNameToOperationKey(toolName, exposed);\n if (!key) {\n return {\n content: [{ type: 'text', text: `Unknown tool: ${toolName}` }],\n isError: true,\n };\n }\n\n // The exposed def carries the CQRS gateway — no second lookup needed.\n const def = exposed.find((op) => op.domain === key.domain && op.operation === key.operation);\n if (!def) {\n return {\n content: [{ type: 'text', text: `Unknown tool: ${toolName}` }],\n isError: true,\n };\n }\n\n const request: DispatchRequest = {\n gateway: def.gateway,\n domain: key.domain,\n operation: key.operation,\n params: args,\n source: 'mcp',\n requestId: randomUUID(),\n };\n\n try {\n const response = await handler.handle(request);\n return responseToToolResult(response);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: 'text', text: `Tool execution failed: ${message}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// JSON-RPC method handlers\n// ---------------------------------------------------------------------------\n\n/**\n * Handle a single decoded JSON-RPC request frame.\n *\n * @param handler - The injected gateway handler.\n * @param req - The decoded JSON-RPC request.\n * @returns The JSON-RPC response frame to write back.\n */\nasync function handleRequest(\n handler: GatewayHandler,\n req: JsonRpcRequest,\n): Promise<JsonRpcResponse> {\n const { id, method, params } = req;\n\n switch (method) {\n case 'initialize':\n return ok(id, {\n protocolVersion: PROTOCOL_VERSION,\n capabilities: { tools: {} },\n serverInfo: { name: SERVER_NAME, version: GATEWAY_MCP_VERSION },\n });\n\n case 'tools/list':\n return ok(id, { tools: buildToolsList() satisfies McpTool[] });\n\n case 'tools/call': {\n const p = params as { name?: string; arguments?: Record<string, unknown> } | undefined;\n const toolName = p?.name;\n if (!toolName) {\n return rpcError(id, -32602, 'Missing tool name in params.name');\n }\n const result = await callTool(handler, toolName, p?.arguments ?? {});\n return ok(id, result);\n }\n\n case 'notifications/initialized':\n case 'ping':\n return ok(id, {});\n\n default:\n return rpcError(id, -32601, `Method not found: ${method}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// stdio server entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Options for {@link startMcpServer}.\n */\nexport interface McpServerOptions {\n /** Inbound stream (defaults to `process.stdin`). */\n input?: NodeJS.ReadableStream;\n /** Outbound stream (defaults to `process.stdout`). */\n output?: NodeJS.WritableStream;\n /**\n * Whether to call `process.exit(0)` when the input stream closes.\n * Defaults to `true` (production stdio). Tests pass `false`.\n */\n exitOnClose?: boolean;\n}\n\n/**\n * Start the MCP stdio server over the gateway.\n *\n * Reads newline-delimited JSON-RPC 2.0 requests from the input stream, routes\n * `tools/call` through the injected {@link GatewayHandler}, and writes responses\n * to the output stream. Each inbound line must be one complete JSON-RPC request.\n *\n * The caller assembles the {@link GatewayHandler} (via `createGatewayHandler`\n * with its domain handlers + middleware) and injects it here — this adapter\n * never builds handlers itself, keeping the runtime free of any\n * `@cleocode/cleo` dependency.\n *\n * @param handler - The transport-neutral gateway handler to route through.\n * @param opts - Optional stream + lifecycle overrides (defaults to stdio).\n */\nexport function startMcpServer(handler: GatewayHandler, opts?: McpServerOptions): void {\n const input = opts?.input ?? process.stdin;\n const output = opts?.output ?? process.stdout;\n const exitOnClose = opts?.exitOnClose ?? true;\n const log = getLogger('gateway-mcp');\n\n const rl = readline.createInterface({ input, terminal: false });\n\n rl.on('line', (line) => {\n const trimmed = line.trim();\n if (!trimmed) return;\n\n let req: JsonRpcRequest;\n try {\n req = JSON.parse(trimmed) as JsonRpcRequest;\n } catch {\n output.write(`${JSON.stringify(rpcError(null, -32700, 'Parse error: invalid JSON'))}\\n`);\n return;\n }\n\n handleRequest(handler, req)\n .then((response) => {\n output.write(`${JSON.stringify(response)}\\n`);\n })\n .catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n log.warn({ err }, 'mcp request handling failed');\n output.write(\n `${JSON.stringify(rpcError(req.id ?? null, -32603, `Internal error: ${message}`))}\\n`,\n );\n });\n });\n\n rl.on('close', () => {\n if (exitOnClose) process.exit(0);\n });\n}\n","/**\n * MCP tool-name <-> gateway-operation mapping.\n *\n * The MCP wire protocol identifies a tool by a single flat `name` string\n * (snake_case, e.g. `cleo_sentient_propose_list`), whereas the gateway routes\n * on a `(gateway, domain, operation)` triple (e.g.\n * `query / sentient / propose.list`). This module is the bijective bridge\n * between the two surfaces:\n *\n * gateway op → MCP tool name {@link operationToToolName}\n * MCP tool name → domain+operation {@link toolNameToOperationKey}\n *\n * ## Naming convention (frozen — matches the legacy `@cleocode/mcp-adapter`)\n *\n * `cleo_<domain>_<operation>` where every `.` and `-` in the dotted operation\n * name becomes `_`. The three historically-exposed tools therefore map as:\n *\n * | gateway op | MCP tool name |\n * |-----------------------------|--------------------------------|\n * | `sentient` · `status` | `cleo_sentient_status` |\n * | `sentient` · `propose.list` | `cleo_sentient_propose_list` |\n * | `sentient` · `propose.enable` | `cleo_sentient_propose_enable` |\n *\n * Preserving this exact mapping is what guarantees R3-T4's \"no behavior change\n * to the MCP tool surface\" acceptance criterion.\n *\n * @task T11448\n * @epic T11254\n * @saga T11243\n */\n\nimport type { OperationDef } from '@cleocode/contracts';\n\n/** Prefix every CLEO MCP tool name carries. */\nexport const MCP_TOOL_PREFIX = 'cleo_';\n\n/**\n * Identifies a gateway operation by its `(domain, operation)` pair. The CQRS\n * gateway resolves which `Gateway` ('query' | 'mutate') the pair belongs to via\n * the operation registry, so the MCP adapter does not need to carry it.\n */\nexport interface GatewayOperationKey {\n /** Canonical domain (e.g. `'sentient'`). */\n readonly domain: string;\n /** Dotted operation name (e.g. `'propose.list'`). */\n readonly operation: string;\n}\n\n/**\n * Convert a dotted operation segment into its MCP-safe snake_case form.\n *\n * @param segment - A domain or operation string (may contain `.`/`-`).\n * @returns The segment with every `.`/`-` replaced by `_`.\n */\nfunction toSnake(segment: string): string {\n return segment.replace(/[.-]/g, '_');\n}\n\n/**\n * Derive the flat MCP tool name for a gateway operation.\n *\n * @param op - An {@link OperationDef} (or any object carrying `domain` +\n * `operation`) from the registry.\n * @returns The MCP tool name, e.g. `'cleo_sentient_propose_list'`.\n */\nexport function operationToToolName(op: Pick<OperationDef, 'domain' | 'operation'>): string {\n return `${MCP_TOOL_PREFIX}${toSnake(op.domain)}_${toSnake(op.operation)}`;\n}\n\n/**\n * Resolve an MCP tool name back to its `(domain, operation)` gateway key.\n *\n * The reverse of {@link operationToToolName} cannot recover dots vs dashes from\n * the snake_case wire form, so resolution is performed against the provided\n * `exposed` set (the same `mcpExposed: true` subset used to build `tools/list`).\n * This keeps the mapping exact and rejects unknown tool names.\n *\n * @param toolName - The MCP `tools/call` tool name.\n * @param exposed - The exposed operation defs (the `mcpExposed` subset).\n * @returns The matching {@link GatewayOperationKey}, or `undefined` when the\n * tool name does not correspond to any exposed operation.\n */\nexport function toolNameToOperationKey(\n toolName: string,\n exposed: readonly OperationDef[],\n): GatewayOperationKey | undefined {\n const match = exposed.find((op) => operationToToolName(op) === toolName);\n return match ? { domain: match.domain, operation: match.operation } : undefined;\n}\n","/**\n * Unified CQRS Dispatch Layer -- Operation Registry (shim)\n *\n * The `OPERATIONS` data array was relocated to\n * `@cleocode/contracts/dispatch/operations-registry` in T10061 / T9833b\n * (E-CLI-BOUNDARY · Saga T9831 SG-ARCH-SOLID). This file is now a thin\n * re-export shim that:\n *\n * 1. Re-exports `OPERATIONS` from contracts so every downstream consumer\n * that already imports `{ OPERATIONS }` from this path continues to\n * compile unchanged.\n * 2. Keeps the pure utility / derivation functions (`resolve`,\n * `validateRequiredParams`, `getByDomain`, etc.) that operate on\n * the data and are rightly colocated with the CLI package.\n *\n * @epic T4820\n * @task T4814, T5241, T5615, T10061\n */\n\n// OperationDef + Resolution live in @cleocode/contracts (SSoT — promoted in\n// T9954 / Phase 0b of SG-ARCH-SOLID Saga T9831 · E-CONTRACTS-FOUNDATION\n// T9832). Imported below for internal use AND re-exported so that every\n// downstream consumer that already imports from './registry.js' continues\n// to compile unchanged.\nimport type { OperationDef, Resolution } from '@cleocode/contracts';\nimport { OPERATIONS as _OPERATIONS } from '@cleocode/contracts';\nimport type { CanonicalDomain, Gateway, Tier } from '@cleocode/contracts/gateway';\n\nexport type { OperationDef, Resolution };\n\n/** The single source of truth for all operations in CLEO (data lives in @cleocode/contracts). */\nexport const OPERATIONS: OperationDef[] = _OPERATIONS;\n\n// ---------------------------------------------------------------------------\n// Gateway Matrix Derivation\n// ---------------------------------------------------------------------------\n\n/**\n * Derive a gateway operation matrix from the registry.\n *\n * Returns `Record<string, string[]>` containing:\n * - All canonical domains with their operations\n *\n * This is the SINGLE derivation point — gateways use this instead of\n * maintaining independent operation lists.\n */\nexport function deriveGatewayMatrix(gateway: Gateway): Record<string, string[]> {\n const matrix: Record<string, string[]> = {};\n\n for (const op of OPERATIONS) {\n if (op.gateway !== gateway) continue;\n if (!matrix[op.domain]) matrix[op.domain] = [];\n matrix[op.domain].push(op.operation);\n }\n\n return matrix;\n}\n\n/**\n * Get all accepted domain names for a gateway (canonical only).\n */\nexport function getGatewayDomains(gateway: Gateway): string[] {\n return Object.keys(deriveGatewayMatrix(gateway));\n}\n\n// ---------------------------------------------------------------------------\n// Lookup & Validation\n// ---------------------------------------------------------------------------\n\n/**\n * Resolves a domain + operation to its registered definition.\n */\nexport function resolve(\n gateway: Gateway,\n domain: string,\n operation: string,\n): Resolution | undefined {\n const def = OPERATIONS.find(\n (o) => o.gateway === gateway && o.domain === domain && o.operation === operation,\n );\n\n if (!def) return undefined;\n\n return { domain: def.domain, operation: def.operation, def };\n}\n\n/**\n * Validates that all required parameters are present in the request.\n * Returns an array of missing parameter keys.\n */\nexport function validateRequiredParams(\n def: OperationDef,\n params?: Record<string, unknown>,\n): string[] {\n if (!def.requiredParams || def.requiredParams.length === 0) return [];\n const provided = params || {};\n // Build a quick lookup so we can honor per-param `allowEmpty` declarations.\n // When a ParamDef sets `allowEmpty: true`, the empty string `\"\"` is a\n // valid value rather than \"missing\" — e.g. `provenance.backfill --since \"\"`\n // walks every reachable tag from the beginning of history.\n const allowEmptyKeys = new Set<string>();\n if (def.params) {\n for (const p of def.params) {\n if (p.allowEmpty === true) allowEmptyKeys.add(p.name);\n }\n }\n return def.requiredParams.filter((key) => {\n const v = provided[key];\n if (v === undefined || v === null) return true;\n if (v === '' && !allowEmptyKeys.has(key)) return true;\n return false;\n });\n}\n\n/** Get all operations for a specific canonical domain. */\nexport function getByDomain(domain: CanonicalDomain): OperationDef[] {\n return OPERATIONS.filter((o) => o.domain === domain);\n}\n\n/** Get all operations for a specific gateway. */\nexport function getByGateway(gateway: Gateway): OperationDef[] {\n return OPERATIONS.filter((o) => o.gateway === gateway);\n}\n\n/** Get all operations available at or below a specific tier. */\nexport function getByTier(tier: Tier): OperationDef[] {\n return OPERATIONS.filter((o) => o.tier <= tier);\n}\n\n/** Get a list of canonical domains that actually have operations registered. */\nexport function getActiveDomains(): CanonicalDomain[] {\n const active = new Set(OPERATIONS.map((o) => o.domain));\n return Array.from(active);\n}\n\n/**\n * Returns summary counts of operations for module validation.\n */\nexport function getCounts(): { query: number; mutate: number; total: number } {\n return {\n query: OPERATIONS.filter((o) => o.gateway === 'query').length,\n mutate: OPERATIONS.filter((o) => o.gateway === 'mutate').length,\n total: OPERATIONS.length,\n };\n}\n\n// Module load validation (dynamic, no hardcoded operation totals)\nconst counts = getCounts();\nif (counts.total !== OPERATIONS.length) {\n console.warn(\n `[Registry] Operation count mismatch: total=${counts.total}, registry=${OPERATIONS.length}`,\n );\n}\n","/**\n * MCP `tools/list` generation from the OPERATIONS registry.\n *\n * The MCP tool surface is **default-deny**: only {@link OperationDef} entries\n * that explicitly set `mcpExposed: true` are surfaced. This replaces the legacy\n * `@cleocode/mcp-adapter`'s hand-maintained `ALL_TOOLS` array with a single\n * generation point driven by the canonical registry — promoting a new tool is\n * now one `mcpExposed: true` edit in `@cleocode/contracts`, not a fork-and-copy\n * into a standalone adapter.\n *\n * @task T11448\n * @epic T11254\n * @saga T11243\n */\n\nimport type { OperationDef, ParamDef } from '@cleocode/contracts';\nimport { OPERATIONS } from '../registry.js';\nimport { operationToToolName } from './tool-naming.js';\nimport type { McpInputProperty, McpTool } from './types.js';\n\n/**\n * The subset of OPERATIONS surfaced over MCP (default-deny `mcpExposed` filter).\n *\n * @returns Every {@link OperationDef} with `mcpExposed === true`, in registry\n * order. Empty when no operation opts in.\n */\nexport function exposedOperations(): OperationDef[] {\n return OPERATIONS.filter((op) => op.mcpExposed === true);\n}\n\n/**\n * Map a JSON-ish {@link ParamDef} type to its JSON Schema primitive.\n *\n * MCP clients consume standard JSON Schema, so `'array'` widens to `'string'`\n * the same way the CLI's citty bridge does (callers split/parse) and every\n * other type passes through unchanged.\n *\n * @param type - The {@link ParamDef} runtime type.\n * @returns The JSON Schema `type` keyword.\n */\nfunction paramTypeToJsonSchema(type: ParamDef['type']): string {\n return type === 'array' ? 'string' : type;\n}\n\n/**\n * Build the MCP `inputSchema` for a single operation from its `params`.\n *\n * Hidden params (`hidden: true`) are excluded from the public tool surface,\n * matching the JSON Schema generation policy declared on {@link ParamDef}.\n *\n * @param op - The exposed operation definition.\n * @returns A JSON Schema object describing the tool's input.\n */\nfunction buildInputSchema(op: OperationDef): McpTool['inputSchema'] {\n const properties: Record<string, McpInputProperty> = {};\n const required: string[] = [];\n\n for (const param of op.params ?? []) {\n if (param.hidden === true) continue;\n const prop: McpInputProperty = {\n type: paramTypeToJsonSchema(param.type),\n description: param.description,\n };\n if (param.enum !== undefined && param.enum.length > 0) {\n prop.enum = param.enum;\n }\n properties[param.name] = prop;\n if (param.required) required.push(param.name);\n }\n\n const schema: McpTool['inputSchema'] = { type: 'object', properties };\n if (required.length > 0) schema.required = required;\n return schema;\n}\n\n/**\n * Convert a single exposed {@link OperationDef} into its MCP tool definition.\n *\n * @param op - An operation with `mcpExposed === true`.\n * @returns The MCP {@link McpTool} (name + description + inputSchema).\n */\nexport function operationToMcpTool(op: OperationDef): McpTool {\n return {\n name: operationToToolName(op),\n description: op.description,\n inputSchema: buildInputSchema(op),\n };\n}\n\n/**\n * Generate the full MCP `tools/list` payload from the registry.\n *\n * @returns Every exposed operation rendered as an {@link McpTool}, in registry\n * order (stable across calls).\n */\nexport function buildToolsList(): McpTool[] {\n return exposedOperations().map(operationToMcpTool);\n}\n"],"mappings":"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","names":[]}
|