@ainecto/mcp 0.3.0 → 0.6.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 +204 -88
- package/dist/bin/ainecto.js +1074 -445
- package/dist/bin/ainecto.js.map +1 -1
- package/dist/bin/mcp.js +156 -72
- package/dist/bin/mcp.js.map +1 -1
- package/package.json +4 -7
package/dist/bin/ainecto.js
CHANGED
|
@@ -5,23 +5,52 @@ import { createHash } from "crypto";
|
|
|
5
5
|
import { mkdir, readFile, rename, rm, stat, writeFile, chmod } from "fs/promises";
|
|
6
6
|
import { homedir } from "os";
|
|
7
7
|
import { dirname, join } from "path";
|
|
8
|
+
|
|
9
|
+
// src/core/harness/role.ts
|
|
10
|
+
var HARNESS_ROLES = ["design", "development", "test", "validation"];
|
|
11
|
+
var ROLE_SCOPE_PREFIX = "ainecto:role:";
|
|
12
|
+
var ROLE_HEADER = "X-AiNecto-Role";
|
|
13
|
+
var ROLE_ENV_VAR = "AINECTO_ROLE";
|
|
14
|
+
function resolveRole(input = {}) {
|
|
15
|
+
if (input.role !== void 0) {
|
|
16
|
+
return { role: assertRole(input.role), source: "flag" };
|
|
17
|
+
}
|
|
18
|
+
const envVars = input.envVars ?? process.env;
|
|
19
|
+
const fromEnv = envVars[ROLE_ENV_VAR];
|
|
20
|
+
if (fromEnv && fromEnv.trim()) {
|
|
21
|
+
return { role: assertRole(fromEnv), source: "env" };
|
|
22
|
+
}
|
|
23
|
+
return { source: "none" };
|
|
24
|
+
}
|
|
25
|
+
function assertRole(value) {
|
|
26
|
+
const normalized = value.trim().toLowerCase();
|
|
27
|
+
if (HARNESS_ROLES.includes(normalized)) {
|
|
28
|
+
return normalized;
|
|
29
|
+
}
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Invalid role "${value}". Expected one of: ${HARNESS_ROLES.join(", ")}.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/core/auth/tokenStore.ts
|
|
8
36
|
var FileTokenStore = class {
|
|
9
37
|
filePath;
|
|
10
38
|
constructor(rootDir = join(homedir(), ".ainecto")) {
|
|
11
39
|
this.filePath = join(rootDir, "tokens.json");
|
|
12
40
|
}
|
|
13
|
-
async load(endpoint
|
|
41
|
+
async load(endpoint) {
|
|
14
42
|
const file = await this.readFile();
|
|
15
|
-
return file[tokenKey(endpoint
|
|
43
|
+
return file[tokenKey(endpoint)];
|
|
16
44
|
}
|
|
17
|
-
async save(endpoint, token
|
|
45
|
+
async save(endpoint, token) {
|
|
46
|
+
assertNoRoleScope(token);
|
|
18
47
|
const file = await this.readFile();
|
|
19
|
-
file[tokenKey(endpoint
|
|
48
|
+
file[tokenKey(endpoint)] = { ...token, endpoint, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
20
49
|
await this.writeFile(file);
|
|
21
50
|
}
|
|
22
|
-
async delete(endpoint
|
|
51
|
+
async delete(endpoint) {
|
|
23
52
|
const file = await this.readFile();
|
|
24
|
-
delete file[tokenKey(endpoint
|
|
53
|
+
delete file[tokenKey(endpoint)];
|
|
25
54
|
await this.writeFile(file);
|
|
26
55
|
}
|
|
27
56
|
async readFile() {
|
|
@@ -56,9 +85,15 @@ var FileTokenStore = class {
|
|
|
56
85
|
await chmodIfPermissive(this.filePath, 384);
|
|
57
86
|
}
|
|
58
87
|
};
|
|
59
|
-
function tokenKey(endpoint
|
|
60
|
-
|
|
61
|
-
|
|
88
|
+
function tokenKey(endpoint) {
|
|
89
|
+
return createHash("sha256").update(endpoint).digest("hex");
|
|
90
|
+
}
|
|
91
|
+
function assertNoRoleScope(token) {
|
|
92
|
+
if (token.scope?.split(/\s+/).some((scope) => scope.startsWith(ROLE_SCOPE_PREFIX))) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
"Refusing to store a role-scoped access token: sign-in is one per machine and the role comes from the repository. Run `ainecto auth login` again."
|
|
95
|
+
);
|
|
96
|
+
}
|
|
62
97
|
}
|
|
63
98
|
function isNodeError(error, code) {
|
|
64
99
|
return error instanceof Error && "code" in error && error.code === code;
|
|
@@ -85,13 +120,9 @@ import { URLSearchParams } from "url";
|
|
|
85
120
|
|
|
86
121
|
// src/core/config/endpoints.ts
|
|
87
122
|
var DEFAULT_ENDPOINTS = {
|
|
88
|
-
prod: "https://
|
|
89
|
-
dev: "https://dev.
|
|
123
|
+
prod: "https://ainecto.com/mcp",
|
|
124
|
+
dev: "https://dev.ainecto.com/mcp"
|
|
90
125
|
};
|
|
91
|
-
var LEGACY_DEFAULT_ENDPOINTS = [
|
|
92
|
-
"https://ainecto.com/mcp",
|
|
93
|
-
"https://dev.ainecto.com/mcp"
|
|
94
|
-
];
|
|
95
126
|
function resolveEndpoint(input = {}) {
|
|
96
127
|
const envVars = input.envVars ?? process.env;
|
|
97
128
|
const env = input.env ?? "prod";
|
|
@@ -114,7 +145,7 @@ function isDefaultEndpointFor(env, endpoint) {
|
|
|
114
145
|
}
|
|
115
146
|
function isDefaultEndpoint(endpoint) {
|
|
116
147
|
const normalized = normalizeEndpoint(endpoint);
|
|
117
|
-
return Object.values(DEFAULT_ENDPOINTS).includes(normalized)
|
|
148
|
+
return Object.values(DEFAULT_ENDPOINTS).includes(normalized);
|
|
118
149
|
}
|
|
119
150
|
function assertTrustedEndpointUrl(value, label = "URL") {
|
|
120
151
|
const url = typeof value === "string" ? new URL(value) : value;
|
|
@@ -154,7 +185,7 @@ async function registerPublicClient(registrationEndpoint, redirectUri, fetchImpl
|
|
|
154
185
|
method: "POST",
|
|
155
186
|
headers: { "content-type": "application/json", accept: "application/json" },
|
|
156
187
|
body: JSON.stringify({
|
|
157
|
-
client_name: "
|
|
188
|
+
client_name: "AiNecto CLI",
|
|
158
189
|
redirect_uris: [redirectUri],
|
|
159
190
|
token_endpoint_auth_method: "none",
|
|
160
191
|
grant_types: ["authorization_code", "refresh_token"],
|
|
@@ -177,32 +208,16 @@ function isRecord(value) {
|
|
|
177
208
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
178
209
|
}
|
|
179
210
|
|
|
180
|
-
// src/core/harness/role.ts
|
|
181
|
-
var HARNESS_ROLES = ["design", "development", "test", "validation"];
|
|
182
|
-
var ROLE_SCOPE_PREFIX = "ai-erd:role:";
|
|
183
|
-
var ROLE_HEADER = "X-AI-ERD-Role";
|
|
184
|
-
var ROLE_ENV_VAR = "AI_ERD_ROLE";
|
|
185
|
-
function resolveRole(input = {}) {
|
|
186
|
-
if (input.role !== void 0) {
|
|
187
|
-
return { role: assertRole(input.role), source: "flag" };
|
|
188
|
-
}
|
|
189
|
-
const fromEnv = (input.envVars ?? process.env)[ROLE_ENV_VAR];
|
|
190
|
-
if (fromEnv && fromEnv.trim()) {
|
|
191
|
-
return { role: assertRole(fromEnv), source: "env" };
|
|
192
|
-
}
|
|
193
|
-
return { source: "none" };
|
|
194
|
-
}
|
|
195
|
-
function assertRole(value) {
|
|
196
|
-
const normalized = value.trim().toLowerCase();
|
|
197
|
-
if (HARNESS_ROLES.includes(normalized)) {
|
|
198
|
-
return normalized;
|
|
199
|
-
}
|
|
200
|
-
throw new Error(
|
|
201
|
-
`Invalid role "${value}". Expected one of: ${HARNESS_ROLES.join(", ")}.`
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
211
|
// src/core/auth/oauth.ts
|
|
212
|
+
var DEFAULT_LOGIN_TIMEOUT_MS = 3e5;
|
|
213
|
+
var OAuthLoginError = class extends Error {
|
|
214
|
+
constructor(code, message) {
|
|
215
|
+
super(message);
|
|
216
|
+
this.code = code;
|
|
217
|
+
this.name = "OAuthLoginError";
|
|
218
|
+
}
|
|
219
|
+
code;
|
|
220
|
+
};
|
|
206
221
|
var OAuthClient = class {
|
|
207
222
|
constructor(options) {
|
|
208
223
|
this.options = options;
|
|
@@ -220,21 +235,27 @@ var OAuthClient = class {
|
|
|
220
235
|
assertEnvTokenEndpointAllowed(this.options.endpoint, this.envVars);
|
|
221
236
|
return envToken;
|
|
222
237
|
}
|
|
223
|
-
const stored = await this.options.tokenStore.load(this.options.endpoint
|
|
238
|
+
const stored = await this.options.tokenStore.load(this.options.endpoint);
|
|
224
239
|
if (!stored) {
|
|
225
240
|
return void 0;
|
|
226
241
|
}
|
|
227
242
|
if (!shouldRefresh(stored)) {
|
|
228
243
|
return stored.accessToken;
|
|
229
244
|
}
|
|
230
|
-
|
|
245
|
+
if (this.options.readOnly) {
|
|
246
|
+
return isExpired(stored) ? void 0 : stored.accessToken;
|
|
247
|
+
}
|
|
248
|
+
if (!stored.refreshToken) {
|
|
249
|
+
return isExpired(stored) ? void 0 : stored.accessToken;
|
|
250
|
+
}
|
|
251
|
+
return this.refreshStoredToken({ ...stored, refreshToken: stored.refreshToken });
|
|
231
252
|
}
|
|
232
253
|
async refreshAfterUnauthorized() {
|
|
233
|
-
const stored = await this.options.tokenStore.load(this.options.endpoint
|
|
234
|
-
if (!stored?.refreshToken) {
|
|
254
|
+
const stored = await this.options.tokenStore.load(this.options.endpoint);
|
|
255
|
+
if (this.options.readOnly || !stored?.refreshToken) {
|
|
235
256
|
return void 0;
|
|
236
257
|
}
|
|
237
|
-
return this.refreshStoredToken(stored);
|
|
258
|
+
return this.refreshStoredToken({ ...stored, refreshToken: stored.refreshToken });
|
|
238
259
|
}
|
|
239
260
|
async login() {
|
|
240
261
|
const pkce = generatePkcePair();
|
|
@@ -251,12 +272,14 @@ var OAuthClient = class {
|
|
|
251
272
|
authorizeUrl.searchParams.set("code_challenge_method", "S256");
|
|
252
273
|
authorizeUrl.searchParams.set("resource", this.options.endpoint);
|
|
253
274
|
authorizeUrl.searchParams.set("state", state);
|
|
254
|
-
if (this.options.role) {
|
|
255
|
-
authorizeUrl.searchParams.set("scope", `mcp ${ROLE_SCOPE_PREFIX}${this.options.role}`);
|
|
256
|
-
}
|
|
257
275
|
assertTrustedEndpointUrl(authorizeUrl, "OAuth authorization URL");
|
|
258
|
-
|
|
259
|
-
|
|
276
|
+
const url = authorizeUrl.toString();
|
|
277
|
+
this.options.onAuthorizeUrl?.(url);
|
|
278
|
+
const browserFailure = this.openBrowserImpl(url).then(() => new Promise(() => void 0));
|
|
279
|
+
const code = await withTimeout(
|
|
280
|
+
Promise.race([loopback.waitForCode(), browserFailure]),
|
|
281
|
+
this.options.loginTimeoutMs ?? DEFAULT_LOGIN_TIMEOUT_MS
|
|
282
|
+
);
|
|
260
283
|
const token = await exchangeToken(metadata.tokenEndpoint, {
|
|
261
284
|
grant_type: "authorization_code",
|
|
262
285
|
code,
|
|
@@ -267,19 +290,17 @@ var OAuthClient = class {
|
|
|
267
290
|
...registration.clientSecret ? { client_secret: registration.clientSecret } : {}
|
|
268
291
|
}, this.fetchImpl);
|
|
269
292
|
const stored = toStoredToken(this.options.endpoint, token, metadata, registration);
|
|
270
|
-
await this.options.tokenStore.save(this.options.endpoint, stored
|
|
293
|
+
await this.options.tokenStore.save(this.options.endpoint, stored);
|
|
271
294
|
return stored;
|
|
272
295
|
} finally {
|
|
273
296
|
await loopback.close();
|
|
274
297
|
}
|
|
275
298
|
}
|
|
276
299
|
async logout() {
|
|
277
|
-
await this.options.tokenStore.delete(this.options.endpoint
|
|
300
|
+
await this.options.tokenStore.delete(this.options.endpoint);
|
|
278
301
|
}
|
|
302
|
+
/** 부르는 쪽이 refresh token 이 있음을 이미 확인했다 — 타입이 그것을 요구한다. */
|
|
279
303
|
async refreshStoredToken(stored) {
|
|
280
|
-
if (!stored.refreshToken) {
|
|
281
|
-
return stored.accessToken;
|
|
282
|
-
}
|
|
283
304
|
const metadata = await discoverOAuthMetadata(this.options.endpoint, this.fetchImpl);
|
|
284
305
|
const token = await exchangeToken(metadata.tokenEndpoint, {
|
|
285
306
|
grant_type: "refresh_token",
|
|
@@ -289,7 +310,7 @@ var OAuthClient = class {
|
|
|
289
310
|
resource: this.options.endpoint
|
|
290
311
|
}, this.fetchImpl);
|
|
291
312
|
const next = toStoredToken(this.options.endpoint, token, metadata, stored);
|
|
292
|
-
await this.options.tokenStore.save(this.options.endpoint, next
|
|
313
|
+
await this.options.tokenStore.save(this.options.endpoint, next);
|
|
293
314
|
return next.accessToken;
|
|
294
315
|
}
|
|
295
316
|
};
|
|
@@ -301,6 +322,27 @@ function generatePkcePair() {
|
|
|
301
322
|
function generateOAuthState() {
|
|
302
323
|
return randomBytes(32).toString("base64url");
|
|
303
324
|
}
|
|
325
|
+
function isExpired(token, now = Date.now()) {
|
|
326
|
+
return token.expiresAt !== void 0 && token.expiresAt <= now;
|
|
327
|
+
}
|
|
328
|
+
async function withTimeout(work, timeoutMs) {
|
|
329
|
+
let timer;
|
|
330
|
+
const timeout = new Promise((_, reject) => {
|
|
331
|
+
timer = setTimeout(() => {
|
|
332
|
+
const minutes = timeoutMs / 6e4;
|
|
333
|
+
const span = Number.isInteger(minutes) ? `${minutes} minute${minutes === 1 ? "" : "s"}` : `${timeoutMs} ms`;
|
|
334
|
+
reject(new OAuthLoginError(
|
|
335
|
+
"LOGIN_TIMEOUT",
|
|
336
|
+
`Sign-in was not completed within ${span}. Nothing was saved \u2014 run the same command again.`
|
|
337
|
+
));
|
|
338
|
+
}, timeoutMs);
|
|
339
|
+
});
|
|
340
|
+
try {
|
|
341
|
+
return await Promise.race([work, timeout]);
|
|
342
|
+
} finally {
|
|
343
|
+
clearTimeout(timer);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
304
346
|
function shouldRefresh(token, now = Date.now()) {
|
|
305
347
|
if (!token.expiresAt) {
|
|
306
348
|
return false;
|
|
@@ -393,11 +435,11 @@ function toStoredToken(endpoint, token, metadata, previous) {
|
|
|
393
435
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
394
436
|
};
|
|
395
437
|
}
|
|
396
|
-
async function createLoopbackReceiver(expectedState) {
|
|
438
|
+
async function createLoopbackReceiver(expectedState, host = "127.0.0.1") {
|
|
397
439
|
let resolveCode;
|
|
398
440
|
let rejectCode;
|
|
399
|
-
const codePromise = new Promise((
|
|
400
|
-
resolveCode =
|
|
441
|
+
const codePromise = new Promise((resolve3, reject) => {
|
|
442
|
+
resolveCode = resolve3;
|
|
401
443
|
rejectCode = reject;
|
|
402
444
|
});
|
|
403
445
|
codePromise.catch(() => void 0);
|
|
@@ -408,13 +450,13 @@ async function createLoopbackReceiver(expectedState) {
|
|
|
408
450
|
const state = url.searchParams.get("state");
|
|
409
451
|
if (error) {
|
|
410
452
|
res.writeHead(400, { "content-type": "text/plain" });
|
|
411
|
-
res.end("
|
|
453
|
+
res.end("AiNecto CLI authorization failed. You can close this tab.");
|
|
412
454
|
rejectCode(new Error(`OAuth authorization failed: ${error}`));
|
|
413
455
|
return;
|
|
414
456
|
}
|
|
415
457
|
if (!state || state !== expectedState) {
|
|
416
458
|
res.writeHead(400, { "content-type": "text/plain" });
|
|
417
|
-
res.end("
|
|
459
|
+
res.end("AiNecto CLI authorization state mismatch. You can close this tab.");
|
|
418
460
|
rejectCode(new Error("OAuth state mismatch."));
|
|
419
461
|
return;
|
|
420
462
|
}
|
|
@@ -425,10 +467,16 @@ async function createLoopbackReceiver(expectedState) {
|
|
|
425
467
|
return;
|
|
426
468
|
}
|
|
427
469
|
res.writeHead(200, { "content-type": "text/plain" });
|
|
428
|
-
res.end("
|
|
470
|
+
res.end("AiNecto CLI authorization complete. You can close this tab.");
|
|
429
471
|
resolveCode(code);
|
|
430
472
|
});
|
|
431
|
-
await new Promise((
|
|
473
|
+
await new Promise((resolve3, reject) => {
|
|
474
|
+
server.once("error", (error) => reject(new OAuthLoginError(
|
|
475
|
+
"CALLBACK_UNAVAILABLE",
|
|
476
|
+
`Could not start the local sign-in callback on ${host} (${error.message}). If this command runs in a sandbox, allow local network binding or run it outside the sandbox.`
|
|
477
|
+
)));
|
|
478
|
+
server.listen(0, host, () => resolve3());
|
|
479
|
+
});
|
|
432
480
|
const address = server.address();
|
|
433
481
|
if (!address || typeof address === "string") {
|
|
434
482
|
throw new Error("Loopback OAuth server did not bind to a TCP port.");
|
|
@@ -436,7 +484,10 @@ async function createLoopbackReceiver(expectedState) {
|
|
|
436
484
|
return {
|
|
437
485
|
redirectUri: `http://127.0.0.1:${address.port}/callback`,
|
|
438
486
|
waitForCode: () => codePromise,
|
|
439
|
-
close: () => new Promise((
|
|
487
|
+
close: () => new Promise((resolve3, reject) => {
|
|
488
|
+
server.closeAllConnections?.();
|
|
489
|
+
server.close((error) => error ? reject(error) : resolve3());
|
|
490
|
+
})
|
|
440
491
|
};
|
|
441
492
|
}
|
|
442
493
|
function parseStringArray(value) {
|
|
@@ -460,10 +511,39 @@ function getBrowserOpenCommand(url, platform = process.platform) {
|
|
|
460
511
|
}
|
|
461
512
|
return { command: "xdg-open", args: [url] };
|
|
462
513
|
}
|
|
463
|
-
|
|
464
|
-
const
|
|
465
|
-
const
|
|
466
|
-
|
|
514
|
+
function launchBrowser(url, deps = {}) {
|
|
515
|
+
const platform = deps.platform ?? process.platform;
|
|
516
|
+
const { command, args } = getBrowserOpenCommand(url, platform);
|
|
517
|
+
const spawnImpl = deps.spawnImpl ?? spawn;
|
|
518
|
+
return new Promise((resolve3, reject) => {
|
|
519
|
+
const unavailable = (reason) => new OAuthLoginError(
|
|
520
|
+
"BROWSER_UNAVAILABLE",
|
|
521
|
+
`Could not open a browser on this machine (${command}: ${reason}). Sign-in needs a browser here.`
|
|
522
|
+
);
|
|
523
|
+
let child;
|
|
524
|
+
try {
|
|
525
|
+
child = spawnImpl(command, args, { stdio: "ignore", detached: true });
|
|
526
|
+
} catch (error) {
|
|
527
|
+
reject(unavailable(error instanceof Error ? error.message : String(error)));
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
child.once("error", (error) => reject(unavailable(error.message)));
|
|
531
|
+
if (platform === "win32") {
|
|
532
|
+
child.once("spawn", () => resolve3());
|
|
533
|
+
} else {
|
|
534
|
+
child.once("exit", (code, signal) => {
|
|
535
|
+
if (code === 0) {
|
|
536
|
+
resolve3();
|
|
537
|
+
} else {
|
|
538
|
+
reject(unavailable(code === null ? `stopped by ${signal ?? "a signal"}` : `exited with code ${code}`));
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
child.unref();
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
function openBrowser(url) {
|
|
546
|
+
return launchBrowser(url);
|
|
467
547
|
}
|
|
468
548
|
function assertEnvTokenEndpointAllowed(endpoint, envVars) {
|
|
469
549
|
if (envVars.AINECTO_ALLOW_CUSTOM_ENDPOINT_TOKEN === "1") {
|
|
@@ -534,6 +614,11 @@ async function readAllStdin(stdin) {
|
|
|
534
614
|
return text;
|
|
535
615
|
}
|
|
536
616
|
|
|
617
|
+
// src/core/version.ts
|
|
618
|
+
var CLI_VERSION = true ? "0.6.0" : "dev";
|
|
619
|
+
var PACKAGE_NAME = "@ainecto/mcp";
|
|
620
|
+
var CLI_USER_AGENT = `${PACKAGE_NAME} ${CLI_VERSION}`;
|
|
621
|
+
|
|
537
622
|
// src/core/mcp/rpcClient.ts
|
|
538
623
|
var McpRpcError = class extends Error {
|
|
539
624
|
constructor(message, code, details) {
|
|
@@ -556,7 +641,7 @@ var McpRpcClient = class {
|
|
|
556
641
|
return this.request("initialize", {
|
|
557
642
|
protocolVersion: "2024-11-05",
|
|
558
643
|
capabilities: {},
|
|
559
|
-
clientInfo: { name:
|
|
644
|
+
clientInfo: { name: PACKAGE_NAME, version: CLI_VERSION }
|
|
560
645
|
});
|
|
561
646
|
}
|
|
562
647
|
async toolsList() {
|
|
@@ -731,55 +816,73 @@ function redactSecretString(value) {
|
|
|
731
816
|
|
|
732
817
|
// src/core/catalog/generated.prod.ts
|
|
733
818
|
var generatedTools = [
|
|
734
|
-
{ "mcpName": "
|
|
735
|
-
{ "mcpName": "
|
|
736
|
-
{ "mcpName": "
|
|
737
|
-
{ "mcpName": "
|
|
738
|
-
{ "mcpName": "
|
|
739
|
-
{ "mcpName": "
|
|
740
|
-
{ "mcpName": "
|
|
741
|
-
{ "mcpName": "
|
|
742
|
-
{ "mcpName": "
|
|
743
|
-
{ "mcpName": "
|
|
744
|
-
{ "mcpName": "
|
|
745
|
-
{ "mcpName": "mcp__ainecto__erd_apply_changes", "commandPath": ["erd", "apply-changes"], "group": "erd", "description": 'Required single-call mutation path for ERD create/update/delete changes. Put tables, columns, indexes, refs, enums, enum_values, and table groups in one operations array and call this once.\nRules: (1) all data field names are snake_case; (2) for create, generate your own UUIDs and put them in data.uuid, then later operations in the same batch may reference those UUIDs without read-back calls; (3) enum values are separate enum_value operations, not inline on enum; (4) ref.relationship uses short dbdiagram-style symbols: >, <, or -; (5) ref_column operations map FK columns explicitly, never automatically.\nDo not look for type-specific ERD CUD tools; this tool is the only ERD CUD entry point. If operations is very large, use mcp__ainecto__request_upload_token with purpose=mcp.call and send the tools/call payload with curl to save LLM tokens.\nOne-call worked example:\n{\n "documentUuid": "erd-doc-uuid",\n "description": "create users/posts ERD",\n "operations": [\n {"op":"create","type":"table","data":{"uuid":"tbl_users","name":"users","display_order":1}},\n {"op":"create","type":"column","data":{"uuid":"col_users_id","table_uuid":"tbl_users","name":"id","type":"bigint","is_pk":true,"is_not_null":true,"display_order":1}},\n {"op":"create","type":"column","data":{"uuid":"col_users_email","table_uuid":"tbl_users","name":"email","type":"varchar(255)","is_unique":true,"is_not_null":true,"display_order":2}},\n {"op":"create","type":"table","data":{"uuid":"tbl_posts","name":"posts","display_order":2}},\n {"op":"create","type":"column","data":{"uuid":"col_posts_id","table_uuid":"tbl_posts","name":"id","type":"bigint","is_pk":true,"is_not_null":true,"display_order":1}},\n {"op":"create","type":"column","data":{"uuid":"col_posts_author_id","table_uuid":"tbl_posts","name":"author_id","type":"bigint","is_not_null":true,"display_order":2}},\n {"op":"create","type":"column","data":{"uuid":"col_posts_status","table_uuid":"tbl_posts","name":"status","type":"post_status","is_not_null":true,"display_order":3}},\n {"op":"create","type":"enum","data":{"uuid":"enum_post_status","name":"post_status"}},\n {"op":"create","type":"enum_value","data":{"uuid":"enum_post_status_draft","enum_uuid":"enum_post_status","value":"draft","display_order":1}},\n {"op":"create","type":"enum_value","data":{"uuid":"enum_post_status_published","enum_uuid":"enum_post_status","value":"published","display_order":2}},\n {"op":"create","type":"ref","data":{"uuid":"ref_posts_author","name":"fk_posts_author","from_table_uuid":"tbl_posts","to_table_uuid":"tbl_users","relationship":">","on_delete":"cascade"}},\n {"op":"create","type":"ref_column","data":{"uuid":"refcol_posts_author","ref_uuid":"ref_posts_author","from_column_uuid":"col_posts_author_id","to_column_uuid":"col_users_id","display_order":1}}\n ]\n}', "inputSchema": { "properties": { "arrangeNewTables": { "description": "Whether to auto-place newly-created tables after the batch; defaults to true", "type": "boolean" }, "description": { "description": "Reason for batch (audit)", "type": "string" }, "documentUuid": { "description": "ERD document UUID", "type": "string" }, "operations": { "description": "Atomic create/update/delete operations", "items": { "description": "One ERD mutation. Use a single operations array with client-generated data.uuid values to create cross-referenced tables, columns, enums, refs, and ref_columns without read-back calls.", "discriminator": { "propertyName": "type" }, "oneOf": [{ "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='table'.", "properties": { "alias": { "description": "Display alias.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "header_color": { "description": "Header color.", "type": "string" }, "name": { "description": "Table name. Required for create.", "type": "string" }, "note": { "description": "Table note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "required": ["name"], "title": "table data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='table'.", "properties": { "alias": { "description": "Display alias.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "header_color": { "description": "Header color.", "type": "string" }, "name": { "description": "Table name. Required for create.", "type": "string" }, "note": { "description": "Table note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "table", "description": "Must be 'table' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "table operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='column'.", "properties": { "default_value": { "description": "Default value.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "is_increment": { "description": "Auto-increment flag.", "type": "boolean" }, "is_not_null": { "description": "Not-null flag.", "type": "boolean" }, "is_pk": { "description": "Primary key flag.", "type": "boolean" }, "is_unique": { "description": "Unique flag.", "type": "boolean" }, "name": { "description": "Column name.", "type": "string" }, "note": { "description": "Column note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create; may be a data.uuid from an earlier table operation in this batch.", "type": "string" }, "type": { "description": "SQL data type. Use this exact field name; do not send data_type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "required": ["table_uuid"], "title": "column data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='column'.", "properties": { "default_value": { "description": "Default value.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "is_increment": { "description": "Auto-increment flag.", "type": "boolean" }, "is_not_null": { "description": "Not-null flag.", "type": "boolean" }, "is_pk": { "description": "Primary key flag.", "type": "boolean" }, "is_unique": { "description": "Unique flag.", "type": "boolean" }, "name": { "description": "Column name.", "type": "string" }, "note": { "description": "Column note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create; may be a data.uuid from an earlier table operation in this batch.", "type": "string" }, "type": { "description": "SQL data type. Use this exact field name; do not send data_type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "column data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "column", "description": "Must be 'column' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "column operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='index'.", "properties": { "is_pk": { "description": "Primary key index flag.", "type": "boolean" }, "is_unique": { "description": "Unique index flag.", "type": "boolean" }, "name": { "description": "Index name.", "type": "string" }, "note": { "description": "Index note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create.", "type": "string" }, "type": { "description": "Index type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "required": ["table_uuid"], "title": "index data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='index'.", "properties": { "is_pk": { "description": "Primary key index flag.", "type": "boolean" }, "is_unique": { "description": "Unique index flag.", "type": "boolean" }, "name": { "description": "Index name.", "type": "string" }, "note": { "description": "Index note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create.", "type": "string" }, "type": { "description": "Index type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "index data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "index", "description": "Must be 'index' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "index operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='index_column'.", "properties": { "column_uuid": { "description": "Column UUID. Required for create.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "index_uuid": { "description": "Index UUID. Required for create.", "type": "string" }, "sort_order": { "description": "Sort order, for example ASC or DESC.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "required": ["index_uuid", "column_uuid"], "title": "index_column data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='index_column'.", "properties": { "column_uuid": { "description": "Column UUID. Required for create.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "index_uuid": { "description": "Index UUID. Required for create.", "type": "string" }, "sort_order": { "description": "Sort order, for example ASC or DESC.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "index_column data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "index_column", "description": "Must be 'index_column' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "index_column operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='ref'.", "properties": { "from_table_uuid": { "description": "Source table UUID. Required for create.", "type": "string" }, "name": { "description": "Reference name.", "type": "string" }, "on_delete": { "description": "ON DELETE action.", "type": "string" }, "on_update": { "description": "ON UPDATE action.", "type": "string" }, "relationship": { "description": "Required for create. Use short dbdiagram-style relationship symbols.", "enum": [">", "<", "-"], "type": "string" }, "to_table_uuid": { "description": "Target table UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "required": ["from_table_uuid", "to_table_uuid", "relationship"], "title": "ref data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='ref'.", "properties": { "from_table_uuid": { "description": "Source table UUID. Required for create.", "type": "string" }, "name": { "description": "Reference name.", "type": "string" }, "on_delete": { "description": "ON DELETE action.", "type": "string" }, "on_update": { "description": "ON UPDATE action.", "type": "string" }, "relationship": { "description": "Required for create. Use short dbdiagram-style relationship symbols.", "enum": [">", "<", "-"], "type": "string" }, "to_table_uuid": { "description": "Target table UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "ref data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "ref", "description": "Must be 'ref' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "ref operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='ref_column'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "from_column_uuid": { "description": "Source column UUID. Required for create.", "type": "string" }, "ref_uuid": { "description": "Reference UUID. Required for create; may be a data.uuid from an earlier ref operation.", "type": "string" }, "to_column_uuid": { "description": "Target column UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "required": ["ref_uuid", "from_column_uuid", "to_column_uuid"], "title": "ref_column data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='ref_column'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "from_column_uuid": { "description": "Source column UUID. Required for create.", "type": "string" }, "ref_uuid": { "description": "Reference UUID. Required for create; may be a data.uuid from an earlier ref operation.", "type": "string" }, "to_column_uuid": { "description": "Target column UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "ref_column data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "ref_column", "description": "Must be 'ref_column' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "ref_column operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='enum'.", "properties": { "name": { "description": "Enum name.", "type": "string" }, "note": { "description": "Enum note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "enum data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='enum'.", "properties": { "name": { "description": "Enum name.", "type": "string" }, "note": { "description": "Enum note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "enum data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "enum", "description": "Must be 'enum' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "enum operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='enum_value'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "enum_uuid": { "description": "Parent enum UUID. Required for create; may be a data.uuid from an earlier enum operation.", "type": "string" }, "note": { "description": "Enum value note.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" }, "value": { "description": "Enum value. Inline enum values on enum are not persisted; create enum_value operations.", "type": "string" } }, "required": ["enum_uuid"], "title": "enum_value data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='enum_value'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "enum_uuid": { "description": "Parent enum UUID. Required for create; may be a data.uuid from an earlier enum operation.", "type": "string" }, "note": { "description": "Enum value note.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" }, "value": { "description": "Enum value. Inline enum values on enum are not persisted; create enum_value operations.", "type": "string" } }, "title": "enum_value data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "enum_value", "description": "Must be 'enum_value' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "enum_value operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='table_group'.", "properties": { "color": { "description": "Group color.", "type": "string" }, "name": { "description": "Table group name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table_group data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='table_group'.", "properties": { "color": { "description": "Group color.", "type": "string" }, "name": { "description": "Table group name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table_group data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "table_group", "description": "Must be 'table_group' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "table_group operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='table_group_table'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "schema_name": { "description": "Table schema name.", "type": "string" }, "table_name": { "description": "Table name. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "required": ["group_uuid", "table_name"], "title": "table_group_table data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='table_group_table'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "schema_name": { "description": "Table schema name.", "type": "string" }, "table_name": { "description": "Table name. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table_group_table data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "table_group_table", "description": "Must be 'table_group_table' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "table_group_table operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='table_group_note'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "note_uuid": { "description": "Note UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "required": ["group_uuid", "note_uuid"], "title": "table_group_note data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='table_group_note'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "note_uuid": { "description": "Note UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table_group_note data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "table_group_note", "description": "Must be 'table_group_note' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "table_group_note operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='filter'.", "properties": { "included_tables": { "description": "JSON string/list of included tables.", "type": "string" }, "is_default": { "description": "Default filter flag.", "type": "boolean" }, "name": { "description": "Filter name.", "type": "string" }, "table_groups": { "description": "JSON string/object of table groups.", "type": "string" }, "table_positions": { "description": "JSON string/object of table positions.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "filter data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='filter'.", "properties": { "included_tables": { "description": "JSON string/list of included tables.", "type": "string" }, "is_default": { "description": "Default filter flag.", "type": "boolean" }, "name": { "description": "Filter name.", "type": "string" }, "table_groups": { "description": "JSON string/object of table groups.", "type": "string" }, "table_positions": { "description": "JSON string/object of table positions.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "filter data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "filter", "description": "Must be 'filter' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "filter operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='note'.", "properties": { "color": { "description": "Note color.", "type": "string" }, "content": { "description": "Note content.", "type": "string" }, "height": { "description": "Height.", "type": "integer" }, "icon_name": { "description": "Icon name.", "type": "string" }, "position_x": { "description": "X position.", "type": "integer" }, "position_y": { "description": "Y position.", "type": "integer" }, "title": { "description": "Note title.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" }, "width": { "description": "Width.", "type": "integer" } }, "title": "note data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='note'.", "properties": { "color": { "description": "Note color.", "type": "string" }, "content": { "description": "Note content.", "type": "string" }, "height": { "description": "Height.", "type": "integer" }, "icon_name": { "description": "Icon name.", "type": "string" }, "position_x": { "description": "X position.", "type": "integer" }, "position_y": { "description": "Y position.", "type": "integer" }, "title": { "description": "Note title.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" }, "width": { "description": "Width.", "type": "integer" } }, "title": "note data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "note", "description": "Must be 'note' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "note operation", "type": "object" }], "properties": { "data": { "description": "Type-specific payload. All field names are snake_case. For create, data.uuid may be client-supplied and referenced by later operations in the same batch.", "oneOf": [{ "description": "Payload for operation.type='table'.", "properties": { "alias": { "description": "Display alias.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "header_color": { "description": "Header color.", "type": "string" }, "name": { "description": "Table name. Required for create.", "type": "string" }, "note": { "description": "Table note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table data", "type": "object" }, { "description": "Payload for operation.type='column'.", "properties": { "default_value": { "description": "Default value.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "is_increment": { "description": "Auto-increment flag.", "type": "boolean" }, "is_not_null": { "description": "Not-null flag.", "type": "boolean" }, "is_pk": { "description": "Primary key flag.", "type": "boolean" }, "is_unique": { "description": "Unique flag.", "type": "boolean" }, "name": { "description": "Column name.", "type": "string" }, "note": { "description": "Column note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create; may be a data.uuid from an earlier table operation in this batch.", "type": "string" }, "type": { "description": "SQL data type. Use this exact field name; do not send data_type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "column data", "type": "object" }, { "description": "Payload for operation.type='index'.", "properties": { "is_pk": { "description": "Primary key index flag.", "type": "boolean" }, "is_unique": { "description": "Unique index flag.", "type": "boolean" }, "name": { "description": "Index name.", "type": "string" }, "note": { "description": "Index note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create.", "type": "string" }, "type": { "description": "Index type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "index data", "type": "object" }, { "description": "Payload for operation.type='index_column'.", "properties": { "column_uuid": { "description": "Column UUID. Required for create.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "index_uuid": { "description": "Index UUID. Required for create.", "type": "string" }, "sort_order": { "description": "Sort order, for example ASC or DESC.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "index_column data", "type": "object" }, { "description": "Payload for operation.type='ref'.", "properties": { "from_table_uuid": { "description": "Source table UUID. Required for create.", "type": "string" }, "name": { "description": "Reference name.", "type": "string" }, "on_delete": { "description": "ON DELETE action.", "type": "string" }, "on_update": { "description": "ON UPDATE action.", "type": "string" }, "relationship": { "description": "Required for create. Use short dbdiagram-style relationship symbols.", "enum": [">", "<", "-"], "type": "string" }, "to_table_uuid": { "description": "Target table UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "ref data", "type": "object" }, { "description": "Payload for operation.type='ref_column'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "from_column_uuid": { "description": "Source column UUID. Required for create.", "type": "string" }, "ref_uuid": { "description": "Reference UUID. Required for create; may be a data.uuid from an earlier ref operation.", "type": "string" }, "to_column_uuid": { "description": "Target column UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "ref_column data", "type": "object" }, { "description": "Payload for operation.type='enum'.", "properties": { "name": { "description": "Enum name.", "type": "string" }, "note": { "description": "Enum note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "enum data", "type": "object" }, { "description": "Payload for operation.type='enum_value'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "enum_uuid": { "description": "Parent enum UUID. Required for create; may be a data.uuid from an earlier enum operation.", "type": "string" }, "note": { "description": "Enum value note.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" }, "value": { "description": "Enum value. Inline enum values on enum are not persisted; create enum_value operations.", "type": "string" } }, "title": "enum_value data", "type": "object" }, { "description": "Payload for operation.type='table_group'.", "properties": { "color": { "description": "Group color.", "type": "string" }, "name": { "description": "Table group name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table_group data", "type": "object" }, { "description": "Payload for operation.type='table_group_table'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "schema_name": { "description": "Table schema name.", "type": "string" }, "table_name": { "description": "Table name. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table_group_table data", "type": "object" }, { "description": "Payload for operation.type='table_group_note'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "note_uuid": { "description": "Note UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "table_group_note data", "type": "object" }, { "description": "Payload for operation.type='filter'.", "properties": { "included_tables": { "description": "JSON string/list of included tables.", "type": "string" }, "is_default": { "description": "Default filter flag.", "type": "boolean" }, "name": { "description": "Filter name.", "type": "string" }, "table_groups": { "description": "JSON string/object of table groups.", "type": "string" }, "table_positions": { "description": "JSON string/object of table positions.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" } }, "title": "filter data", "type": "object" }, { "description": "Payload for operation.type='note'.", "properties": { "color": { "description": "Note color.", "type": "string" }, "content": { "description": "Note content.", "type": "string" }, "height": { "description": "Height.", "type": "integer" }, "icon_name": { "description": "Icon name.", "type": "string" }, "position_x": { "description": "X position.", "type": "integer" }, "position_y": { "description": "Y position.", "type": "integer" }, "title": { "description": "Note title.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID. If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "type": "string" }, "width": { "description": "Width.", "type": "integer" } }, "title": "note data", "type": "object" }], "type": "object" }, "op": { "description": "Operation. For create, put any new UUID in data.uuid; for update/delete, put target UUID in operation.uuid.", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "description": "ERD entity type. This selects the operation.data shape.", "enum": ["table", "column", "index", "index_column", "ref", "ref_column", "enum", "enum_value", "table_group", "table_group_table", "table_group_note", "filter", "note"], "type": "string" }, "uuid": { "description": "Target UUID for update/delete. Do not use this for create; use data.uuid instead.", "type": "string" } }, "required": ["op", "type"], "type": "object" }, "type": "array" } }, "required": ["documentUuid", "description", "operations"], "type": "object" }, "required": ["description", "documentUuid", "operations"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "dd64d1f996060ab1" },
|
|
746
|
-
{ "mcpName": "
|
|
747
|
-
{ "mcpName": "
|
|
748
|
-
{ "mcpName": "
|
|
749
|
-
{ "mcpName": "
|
|
750
|
-
{ "mcpName": "
|
|
751
|
-
{ "mcpName": "
|
|
752
|
-
{ "mcpName": "
|
|
753
|
-
{ "mcpName": "
|
|
754
|
-
{ "mcpName": "
|
|
755
|
-
{ "mcpName": "
|
|
756
|
-
{ "mcpName": "
|
|
757
|
-
{ "mcpName": "mcp__ainecto__get_project", "commandPath": ["project", "get"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuid": { "description": "Project UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "b4ee9097f4b80df4" },
|
|
758
|
-
{ "mcpName": "
|
|
759
|
-
{ "mcpName": "
|
|
760
|
-
{ "mcpName": "
|
|
761
|
-
{ "mcpName": "
|
|
762
|
-
{ "mcpName": "
|
|
763
|
-
{ "mcpName": "
|
|
764
|
-
{ "mcpName": "
|
|
765
|
-
{ "mcpName": "
|
|
766
|
-
{ "mcpName": "
|
|
767
|
-
{ "mcpName": "
|
|
768
|
-
{ "mcpName": "
|
|
769
|
-
{ "mcpName": "
|
|
770
|
-
{ "mcpName": "
|
|
771
|
-
{ "mcpName": "
|
|
772
|
-
{ "mcpName": "
|
|
773
|
-
{ "mcpName": "
|
|
774
|
-
{ "mcpName": "
|
|
775
|
-
{ "mcpName": "
|
|
776
|
-
{ "mcpName": "
|
|
777
|
-
{ "mcpName": "
|
|
778
|
-
{ "mcpName": "
|
|
779
|
-
{ "mcpName": "
|
|
780
|
-
{ "mcpName": "
|
|
781
|
-
{ "mcpName": "
|
|
782
|
-
{ "mcpName": "
|
|
819
|
+
{ "mcpName": "create_comments", "commandPath": ["comments", "create"], "group": "generic", "description": "Add comments, questions, or replies to a document. Anyone who can read the document may comment and reply. Set parentCommentId to reply to a top-level comment. To mention people, get their userId and displayName from list_mention_candidates, write @<displayName> in the body, and list their userIds in mentionUserIds (clientMutationId is then required). Items are applied in order and each one commits on its own; results report each item (ok, or status and error).", "inputSchema": { "additionalProperties": false, "properties": { "documentUuid": { "description": "Document UUID the comments belong to", "type": "string" }, "items": { "description": "Comments to add, applied in order", "items": { "additionalProperties": false, "properties": { "body": { "description": "Comment text (plain text, 1..10000 characters). To mention someone, include @<displayName> exactly as returned by mcp__ainecto__list_mention_candidates.", "maxLength": 1e4, "minLength": 1, "type": "string" }, "clientMutationId": { "description": "Idempotency key (visible ASCII, at most 128 characters). Required when mentionUserIds is not empty; reuse the same value when retrying the same item so the mention email is not sent twice.", "maxLength": 128, "type": "string" }, "kind": { "description": "Top-level comments only: COMMENT (default) or QUESTION. A QUESTION can later be resolved with mcp__ainecto__resolve_comments.", "enum": ["COMMENT", "QUESTION"], "type": "string" }, "mentionUserIds": { "description": "userIds to mention (from mcp__ainecto__list_mention_candidates). Each one's @<displayName> must appear in body; newly mentioned people get a notification email.", "items": { "type": "string" }, "maxItems": 20, "type": "array", "uniqueItems": true }, "parentCommentId": { "description": "Reply to this top-level comment id (from mcp__ainecto__list_comments). Replies are one level deep: replying to a reply is rejected.", "minimum": 1, "type": "integer" } }, "required": ["body"], "type": "object" }, "minItems": 1, "type": "array" }, "mailLanguage": { "description": "Language of the mention notification email. Defaults to en.", "enum": ["en", "ko"], "type": "string" } }, "required": ["documentUuid", "items"], "type": "object" }, "required": ["documentUuid", "items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "a673b909d16835ef" },
|
|
820
|
+
{ "mcpName": "create_documents", "commandPath": ["documents", "create"], "group": "document", "description": "Create documents. If a document body is tens of KB or more, prefer request_upload_token (purpose=document.create) and upload the body with curl.", "inputSchema": { "properties": { "items": { "description": "Documents", "items": { "properties": { "body": { "description": "Deprecated compatibility alias for typePayload.body when document type is markdown. \uBCF8\uBB38\uC774 \uC218\uC2ED KB \uC774\uC0C1\uC73C\uB85C \uD06C\uBA74 LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 `request_upload_token (purpose=document.create)` \uB97C \uD638\uCD9C\uD574 uploadUrl \uC744 \uBC1B\uACE0 curl \uB85C \uC9C1\uC811 PUT \uC73C\uB85C \uBCF4\uB0BC \uAC83.", "type": "string" }, "folderUuid": { "description": "Optional folder UUID", "type": "string" }, "projectUuid": { "description": "Project UUID", "type": "string" }, "title": { "description": "Document title", "type": "string" }, "type": { "description": "Document type. Creatable values: erd, markdown, diagram, openapi, html, file.", "enum": ["erd", "markdown", "diagram", "openapi", "html", "file"], "type": "string" }, "typePayload": { "description": 'Type-specific payload for enabled document types. markdown: {body}; diagram: {diagramSource,sourceLanguage,edgeStyle,isModule,metadata}; openapi: {content}; html: {content} (a full HTML page); file: no JSON payload \u2014 bytes go through request_upload_token(purpose=document.create, scopeJson={projectUuid,folderUuid?,type:"file",title,filename}) then one curl PUT (images\xB7PDF\xB7mp4/webm\xB7audio\xB7text/code\xB7CSV only).', "type": "object" } }, "required": ["projectUuid", "type", "title"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "aa868b0e2821006a" },
|
|
821
|
+
{ "mcpName": "create_folders", "commandPath": ["folders", "create"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Folders", "items": { "properties": { "name": { "description": "Folder name", "type": "string" }, "parentFolderUuid": { "description": "Parent folder UUID", "type": "string" }, "projectUuid": { "description": "Project UUID", "type": "string" } }, "required": ["projectUuid", "name"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "30f45b887259609f" },
|
|
822
|
+
{ "mcpName": "create_projects", "commandPath": ["projects", "create"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Projects", "items": { "properties": { "description": { "description": "Description", "type": "string" }, "name": { "description": "Project name", "type": "string" }, "projectKey": { "description": "Unique project key", "type": "string" }, "workspaceUuid": { "description": "Workspace UUID \u2014 null uses user's default workspace", "type": "string" } }, "required": ["name"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "edf5cf688d7c039a" },
|
|
823
|
+
{ "mcpName": "create_workspaces", "commandPath": ["workspaces", "create"], "group": "workspace", "description": "", "inputSchema": { "properties": { "items": { "description": "Workspaces", "items": { "properties": { "description": { "description": "Description", "type": "string" }, "name": { "description": "Workspace name", "type": "string" } }, "required": ["name"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "d9d1b0d561828b8e" },
|
|
824
|
+
{ "mcpName": "delete_attachments", "commandPath": ["attachments", "delete"], "group": "attachment", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Attachment UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "b22c24bffdfe3d7d" },
|
|
825
|
+
{ "mcpName": "delete_comments", "commandPath": ["comments", "delete"], "group": "generic", "description": "Delete your own comments on a document. Only the author can delete. A resolved question and a top-level comment that still has replies cannot be deleted. Each id is applied on its own; results report each id.", "inputSchema": { "additionalProperties": false, "properties": { "documentUuid": { "description": "Document UUID the comments belong to", "type": "string" }, "ids": { "description": "Comment ids to delete (from mcp__ainecto__list_comments)", "items": { "minimum": 1, "type": "integer" }, "minItems": 1, "type": "array" } }, "required": ["documentUuid", "ids"], "type": "object" }, "required": ["documentUuid", "ids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "17b9658b5a47f866" },
|
|
826
|
+
{ "mcpName": "delete_documents", "commandPath": ["documents", "delete"], "group": "document", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Document UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "3a725ae70956c270" },
|
|
827
|
+
{ "mcpName": "delete_folders", "commandPath": ["folders", "delete"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Folder UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "2254cde89c9e8f06" },
|
|
828
|
+
{ "mcpName": "delete_projects", "commandPath": ["projects", "delete"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Project UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "6521c33a28cb5465" },
|
|
829
|
+
{ "mcpName": "delete_workspaces", "commandPath": ["workspaces", "delete"], "group": "workspace", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Workspace UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "3177868221137066" },
|
|
830
|
+
{ "mcpName": "diagram_apply_changes", "commandPath": ["tools", "run", "diagram-apply-changes"], "group": "generic", "description": "Required single-call mutation path for diagram node/edge/group create/update/delete changes. Put nodes, edges, and groups in one request and call this once. Do not look for type-specific diagram CUD tools; this tool is the only diagram CUD entry point.", "inputSchema": { "properties": { "description": { "description": "Reason for batch (audit)", "type": "string" }, "diagramUuid": { "description": "Diagram UUID", "type": "string" }, "edges": { "description": "Edge operations", "items": { "properties": { "action": { "description": "Operation kind", "enum": ["create", "update", "delete"], "type": "string" }, "bridgeType": { "description": "Omit (null) for a plain node-to-node edge. Set only when a group is an endpoint: GROUP_TO_GROUP|NODE_TO_GROUP|GROUP_TO_NODE", "enum": ["GROUP_TO_GROUP", "NODE_TO_GROUP", "GROUP_TO_NODE"], "type": "string" }, "fromUuid": { "description": "Source", "type": "string" }, "label": { "description": "Edge label", "type": "string" }, "lineStyle": { "description": "Line style", "type": "string" }, "strokeColor": { "description": "Stroke color", "type": "string" }, "strokeWidth": { "description": "Stroke width", "type": "integer" }, "toUuid": { "description": "Target", "type": "string" }, "uuid": { "description": "Required for update/delete", "type": "string" } }, "required": ["action"], "type": "object" }, "type": "array" }, "groups": { "description": "Group operations", "items": { "properties": { "action": { "description": "Operation kind", "enum": ["create", "update", "delete"], "type": "string" }, "collapsed": { "description": "Collapsed state", "type": "boolean" }, "color": { "description": "Group color", "type": "string" }, "height": { "description": "Explicit height; omit to keep it derived from member nodes", "type": "number" }, "linkMode": { "description": "Link mode", "type": "string" }, "linkedDiagramUuid": { "description": "Linked diagram UUID", "type": "string" }, "name": { "description": "Group name", "type": "string" }, "parentGroupUuid": { "description": "Parent group UUID", "type": "string" }, "sortOrder": { "description": "Sort order", "type": "integer" }, "uuid": { "description": "Required for update/delete", "type": "string" }, "width": { "description": "Explicit width; omit to keep it derived from member nodes", "type": "number" } }, "required": ["action"], "type": "object" }, "type": "array" }, "nodes": { "description": "Node operations", "items": { "properties": { "action": { "description": "Operation kind", "enum": ["create", "update", "delete"], "type": "string" }, "fillColor": { "description": "Fill color", "type": "string" }, "groupUuid": { "description": "Group UUID; empty string clears membership", "type": "string" }, "height": { "description": "Height", "type": "number" }, "shape": { "description": "Node shape (start/end/diamond/cylinder/parallelogram/stadium/hexagon/rect/roundRect/...)", "enum": ["rect", "roundRect", "stadium", "diamond", "decision", "circle", "start", "doubleCircle", "end", "hexagon", "cylinder", "parallelogram"], "type": "string" }, "strokeColor": { "description": "Stroke color", "type": "string" }, "text": { "description": "Node text/label. Supports newlines (\\n) for multi-line display.", "maxLength": 4e3, "type": "string" }, "textColor": { "description": "Text color", "type": "string" }, "type": { "description": "Node type", "type": "string" }, "uuid": { "description": "Required for update/delete; optional for create", "type": "string" }, "width": { "description": "Width", "type": "number" }, "x": { "description": "X", "type": "number" }, "y": { "description": "Y", "type": "number" } }, "required": ["action"], "type": "object" }, "type": "array" } }, "required": ["diagramUuid", "description"], "type": "object" }, "required": ["description", "diagramUuid"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "2c829103f86f4168" },
|
|
831
|
+
{ "mcpName": "diagram_get_edge", "commandPath": ["tools", "run", "diagram-get-edge"], "group": "generic", "description": "", "inputSchema": { "properties": { "edgeUuid": { "description": "Edge UUID", "type": "string" } }, "required": ["edgeUuid"], "type": "object" }, "required": ["edgeUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "2c03c4f7daa69898" },
|
|
832
|
+
{ "mcpName": "diagram_get_group", "commandPath": ["tools", "run", "diagram-get-group"], "group": "generic", "description": "", "inputSchema": { "properties": { "groupUuid": { "description": "Group UUID", "type": "string" } }, "required": ["groupUuid"], "type": "object" }, "required": ["groupUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "55c5aa27b8cf489d" },
|
|
833
|
+
{ "mcpName": "diagram_get_node", "commandPath": ["tools", "run", "diagram-get-node"], "group": "generic", "description": "", "inputSchema": { "properties": { "nodeUuid": { "description": "Node UUID", "type": "string" } }, "required": ["nodeUuid"], "type": "object" }, "required": ["nodeUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "646d553238328a9e" },
|
|
834
|
+
{ "mcpName": "diagram_get_pending_llm_requests", "commandPath": ["tools", "run", "diagram-get-pending-llm-requests"], "group": "generic", "description": "", "inputSchema": { "properties": {}, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "2143d616cf7822bf" },
|
|
835
|
+
{ "mcpName": "diagram_list_edges", "commandPath": ["tools", "run", "diagram-list-edges"], "group": "generic", "description": "", "inputSchema": { "properties": { "diagramUuid": { "description": "Diagram document UUID", "type": "string" } }, "required": ["diagramUuid"], "type": "object" }, "required": ["diagramUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "4645085bde616381" },
|
|
836
|
+
{ "mcpName": "diagram_list_groups", "commandPath": ["tools", "run", "diagram-list-groups"], "group": "generic", "description": "", "inputSchema": { "properties": { "diagramUuid": { "description": "Diagram document UUID", "type": "string" } }, "required": ["diagramUuid"], "type": "object" }, "required": ["diagramUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "8a7ca45dd34d1aef" },
|
|
837
|
+
{ "mcpName": "diagram_list_nodes", "commandPath": ["tools", "run", "diagram-list-nodes"], "group": "generic", "description": "", "inputSchema": { "properties": { "diagramUuid": { "description": "Diagram document UUID", "type": "string" } }, "required": ["diagramUuid"], "type": "object" }, "required": ["diagramUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "d444066b5ca6eeb4" },
|
|
838
|
+
{ "mcpName": "diagram_submit_llm_result", "commandPath": ["tools", "run", "diagram-submit-llm-result"], "group": "generic", "description": "", "inputSchema": { "properties": { "cachedInputTokens": { "description": "Cached subset of provider total input tokens", "type": "integer" }, "creditsCharged": { "description": "Deprecated; ignored. Credits are calculated server-side from tokens.", "type": "integer" }, "inputTokens": { "description": "Provider total input tokens; Java persists uncached input after one normalization", "type": "integer" }, "outputTokens": { "description": "Output token count", "type": "integer" }, "result": { "description": "LLM result text", "type": "string" }, "uuid": { "description": "LLM request UUID", "type": "string" } }, "required": ["uuid", "result"], "type": "object" }, "required": ["result", "uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "870ace32642a770a" },
|
|
839
|
+
{ "mcpName": "diagram_update_llm_request_status", "commandPath": ["tools", "run", "diagram-update-llm-request-status"], "group": "generic", "description": "", "inputSchema": { "properties": { "errorMessage": { "description": "Error message (when status=FAILED)", "type": "string" }, "status": { "description": "Target status \u2014 currently only FAILED is mutable from caller side", "enum": ["FAILED"], "type": "string" }, "uuid": { "description": "LLM request UUID", "type": "string" } }, "required": ["uuid", "status"], "type": "object" }, "required": ["status", "uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "9ad96e3dc5fd2a8f" },
|
|
840
|
+
{ "mcpName": "disable_shares", "commandPath": ["tools", "run", "disable-shares"], "group": "generic", "description": "", "inputSchema": { "properties": { "targetType": { "description": "Share target type", "enum": ["project", "folder", "document"], "type": "string" }, "targetUuids": { "description": "Target UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["targetType", "targetUuids"], "type": "object" }, "required": ["targetType", "targetUuids"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "bd1b310200b78420" },
|
|
841
|
+
{ "mcpName": "enable_shares", "commandPath": ["tools", "run", "enable-shares"], "group": "generic", "description": "", "inputSchema": { "properties": { "targetType": { "description": "Share target type. A document gets a shareUrl anyone can open; a project gets a public API path only (there is no public project page); a folder token is stored but nothing public reads it yet.", "enum": ["project", "folder", "document"], "type": "string" }, "targetUuids": { "description": "Target UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["targetType", "targetUuids"], "type": "object" }, "required": ["targetType", "targetUuids"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "18559d7c0af09d2c" },
|
|
842
|
+
{ "mcpName": "erd_apply_changes", "commandPath": ["erd", "apply-changes"], "group": "erd", "description": 'Required single-call mutation path for ERD create/update/delete changes. Put tables, columns, indexes, refs, enums, enum_values, and table groups in one operations array and call this once.\nRules: (1) all data field names are snake_case; (2) for create, generate your own UUIDs and put them in data.uuid, then later operations in the same batch may reference those UUIDs without read-back calls; (3) enum values are separate enum_value operations, not inline on enum; (4) ref.relationship uses short dbdiagram-style symbols: >, <, or -; (5) ref_column operations map FK columns explicitly, never automatically.\nDo not look for type-specific ERD CUD tools; this tool is the only ERD CUD entry point. If operations is very large, use request_upload_token with purpose=mcp.call and send the tools/call payload with curl to save LLM tokens.\nOne-call worked example:\n{\n "documentUuid": "erd-doc-uuid",\n "description": "create users/posts ERD",\n "operations": [\n {"op":"create","type":"table","data":{"uuid":"tbl_users","name":"users","display_order":1}},\n {"op":"create","type":"column","data":{"uuid":"col_users_id","table_uuid":"tbl_users","name":"id","type":"bigint","is_pk":true,"is_not_null":true,"display_order":1}},\n {"op":"create","type":"column","data":{"uuid":"col_users_email","table_uuid":"tbl_users","name":"email","type":"varchar(255)","is_unique":true,"is_not_null":true,"display_order":2}},\n {"op":"create","type":"table","data":{"uuid":"tbl_posts","name":"posts","display_order":2}},\n {"op":"create","type":"column","data":{"uuid":"col_posts_id","table_uuid":"tbl_posts","name":"id","type":"bigint","is_pk":true,"is_not_null":true,"display_order":1}},\n {"op":"create","type":"column","data":{"uuid":"col_posts_author_id","table_uuid":"tbl_posts","name":"author_id","type":"bigint","is_not_null":true,"display_order":2}},\n {"op":"create","type":"column","data":{"uuid":"col_posts_status","table_uuid":"tbl_posts","name":"status","type":"post_status","is_not_null":true,"display_order":3}},\n {"op":"create","type":"enum","data":{"uuid":"enum_post_status","name":"post_status"}},\n {"op":"create","type":"enum_value","data":{"uuid":"enum_post_status_draft","enum_uuid":"enum_post_status","value":"draft","display_order":1}},\n {"op":"create","type":"enum_value","data":{"uuid":"enum_post_status_published","enum_uuid":"enum_post_status","value":"published","display_order":2}},\n {"op":"create","type":"ref","data":{"uuid":"ref_posts_author","name":"fk_posts_author","from_table_uuid":"tbl_posts","to_table_uuid":"tbl_users","relationship":">","on_delete":"cascade"}},\n {"op":"create","type":"ref_column","data":{"uuid":"refcol_posts_author","ref_uuid":"ref_posts_author","from_column_uuid":"col_posts_author_id","to_column_uuid":"col_users_id","display_order":1}}\n ]\n}', "inputSchema": { "properties": { "arrangeNewTables": { "description": "Whether to auto-place newly-created tables after the batch; defaults to true", "type": "boolean" }, "description": { "description": "Reason for batch (audit)", "type": "string" }, "documentUuid": { "description": "ERD document UUID", "type": "string" }, "operations": { "description": "Atomic create/update/delete operations", "items": { "description": "One ERD mutation. Use a single operations array with client-generated data.uuid values to create cross-referenced tables, columns, enums, refs, and ref_columns without read-back calls.", "discriminator": { "propertyName": "type" }, "oneOf": [{ "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='table'.", "properties": { "alias": { "description": "Display alias.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "header_color": { "description": "Header color.", "type": "string" }, "name": { "description": "Table name. Required for create.", "type": "string" }, "note": { "description": "Table note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "required": ["name"], "title": "table data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='table'.", "properties": { "alias": { "description": "Display alias.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "header_color": { "description": "Header color.", "type": "string" }, "name": { "description": "Table name. Required for create.", "type": "string" }, "note": { "description": "Table note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "table", "description": "Must be 'table' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "table operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='column'.", "properties": { "default_value": { "description": "Default value.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "is_increment": { "description": "Auto-increment flag.", "type": "boolean" }, "is_not_null": { "description": "Not-null flag.", "type": "boolean" }, "is_pk": { "description": "Primary key flag.", "type": "boolean" }, "is_unique": { "description": "Unique flag.", "type": "boolean" }, "name": { "description": "Column name.", "type": "string" }, "note": { "description": "Column note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create; may be a data.uuid from an earlier table operation in this batch.", "type": "string" }, "type": { "description": "SQL data type. Use this exact field name; do not send data_type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "required": ["table_uuid"], "title": "column data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='column'.", "properties": { "default_value": { "description": "Default value.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "is_increment": { "description": "Auto-increment flag.", "type": "boolean" }, "is_not_null": { "description": "Not-null flag.", "type": "boolean" }, "is_pk": { "description": "Primary key flag.", "type": "boolean" }, "is_unique": { "description": "Unique flag.", "type": "boolean" }, "name": { "description": "Column name.", "type": "string" }, "note": { "description": "Column note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create; may be a data.uuid from an earlier table operation in this batch.", "type": "string" }, "type": { "description": "SQL data type. Use this exact field name; do not send data_type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "column data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "column", "description": "Must be 'column' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "column operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='index'.", "properties": { "is_pk": { "description": "Primary key index flag.", "type": "boolean" }, "is_unique": { "description": "Unique index flag.", "type": "boolean" }, "name": { "description": "Index name.", "type": "string" }, "note": { "description": "Index note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create.", "type": "string" }, "type": { "description": "Index type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "required": ["table_uuid"], "title": "index data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='index'.", "properties": { "is_pk": { "description": "Primary key index flag.", "type": "boolean" }, "is_unique": { "description": "Unique index flag.", "type": "boolean" }, "name": { "description": "Index name.", "type": "string" }, "note": { "description": "Index note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create.", "type": "string" }, "type": { "description": "Index type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "index data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "index", "description": "Must be 'index' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "index operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='index_column'.", "properties": { "column_uuid": { "description": "Column UUID. Required for create.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "index_uuid": { "description": "Index UUID. Required for create.", "type": "string" }, "sort_order": { "description": "Sort order, for example ASC or DESC.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "required": ["index_uuid", "column_uuid"], "title": "index_column data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='index_column'.", "properties": { "column_uuid": { "description": "Column UUID. Required for create.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "index_uuid": { "description": "Index UUID. Required for create.", "type": "string" }, "sort_order": { "description": "Sort order, for example ASC or DESC.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "index_column data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "index_column", "description": "Must be 'index_column' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "index_column operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='ref'.", "properties": { "from_table_uuid": { "description": "Source table UUID. Required for create.", "type": "string" }, "name": { "description": "Reference name.", "type": "string" }, "on_delete": { "description": "ON DELETE action.", "type": "string" }, "on_update": { "description": "ON UPDATE action.", "type": "string" }, "relationship": { "description": "Required for create. Use short dbdiagram-style relationship symbols.", "enum": [">", "<", "-"], "type": "string" }, "to_table_uuid": { "description": "Target table UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "required": ["from_table_uuid", "to_table_uuid", "relationship"], "title": "ref data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='ref'.", "properties": { "from_table_uuid": { "description": "Source table UUID. Required for create.", "type": "string" }, "name": { "description": "Reference name.", "type": "string" }, "on_delete": { "description": "ON DELETE action.", "type": "string" }, "on_update": { "description": "ON UPDATE action.", "type": "string" }, "relationship": { "description": "Required for create. Use short dbdiagram-style relationship symbols.", "enum": [">", "<", "-"], "type": "string" }, "to_table_uuid": { "description": "Target table UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "ref data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "ref", "description": "Must be 'ref' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "ref operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='ref_column'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "from_column_uuid": { "description": "Source column UUID. Required for create.", "type": "string" }, "ref_uuid": { "description": "Reference UUID. Required for create; may be a data.uuid from an earlier ref operation.", "type": "string" }, "to_column_uuid": { "description": "Target column UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "required": ["ref_uuid", "from_column_uuid", "to_column_uuid"], "title": "ref_column data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='ref_column'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "from_column_uuid": { "description": "Source column UUID. Required for create.", "type": "string" }, "ref_uuid": { "description": "Reference UUID. Required for create; may be a data.uuid from an earlier ref operation.", "type": "string" }, "to_column_uuid": { "description": "Target column UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "ref_column data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "ref_column", "description": "Must be 'ref_column' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "ref_column operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='enum'.", "properties": { "name": { "description": "Enum name.", "type": "string" }, "note": { "description": "Enum note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "enum data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='enum'.", "properties": { "name": { "description": "Enum name.", "type": "string" }, "note": { "description": "Enum note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "enum data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "enum", "description": "Must be 'enum' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "enum operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='enum_value'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "enum_uuid": { "description": "Parent enum UUID. Required for create; may be a data.uuid from an earlier enum operation.", "type": "string" }, "note": { "description": "Enum value note.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" }, "value": { "description": "Enum value. Inline enum values on enum are not persisted; create enum_value operations.", "type": "string" } }, "required": ["enum_uuid"], "title": "enum_value data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='enum_value'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "enum_uuid": { "description": "Parent enum UUID. Required for create; may be a data.uuid from an earlier enum operation.", "type": "string" }, "note": { "description": "Enum value note.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" }, "value": { "description": "Enum value. Inline enum values on enum are not persisted; create enum_value operations.", "type": "string" } }, "title": "enum_value data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "enum_value", "description": "Must be 'enum_value' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "enum_value operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='table_group'.", "properties": { "color": { "description": "Group color.", "type": "string" }, "name": { "description": "Table group name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table_group data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='table_group'.", "properties": { "color": { "description": "Group color.", "type": "string" }, "name": { "description": "Table group name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table_group data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "table_group", "description": "Must be 'table_group' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "table_group operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='table_group_table'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "schema_name": { "description": "Table schema name.", "type": "string" }, "table_name": { "description": "Table name. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "required": ["group_uuid", "table_name"], "title": "table_group_table data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='table_group_table'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "schema_name": { "description": "Table schema name.", "type": "string" }, "table_name": { "description": "Table name. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table_group_table data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "table_group_table", "description": "Must be 'table_group_table' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "table_group_table operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='table_group_note'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "note_uuid": { "description": "Note UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "required": ["group_uuid", "note_uuid"], "title": "table_group_note data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='table_group_note'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "note_uuid": { "description": "Note UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table_group_note data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "table_group_note", "description": "Must be 'table_group_note' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "table_group_note operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='filter'.", "properties": { "included_tables": { "description": "JSON string/list of included tables.", "type": "string" }, "is_default": { "description": "Default filter flag.", "type": "boolean" }, "name": { "description": "Filter name.", "type": "string" }, "table_groups": { "description": "JSON string/object of table groups.", "type": "string" }, "table_positions": { "description": "JSON string/object of table positions.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "filter data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='filter'.", "properties": { "included_tables": { "description": "JSON string/list of included tables.", "type": "string" }, "is_default": { "description": "Default filter flag.", "type": "boolean" }, "name": { "description": "Filter name.", "type": "string" }, "table_groups": { "description": "JSON string/object of table groups.", "type": "string" }, "table_positions": { "description": "JSON string/object of table positions.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "filter data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "filter", "description": "Must be 'filter' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "filter operation", "type": "object" }, { "allOf": [{ "if": { "properties": { "op": { "const": "create", "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": { "data": { "description": "Payload for operation.type='note'.", "properties": { "color": { "description": "Note color.", "type": "string" }, "content": { "description": "Note content.", "type": "string" }, "height": { "description": "Height.", "type": "integer" }, "icon_name": { "description": "Icon name.", "type": "string" }, "position_x": { "description": "X position.", "type": "integer" }, "position_y": { "description": "Y position.", "type": "integer" }, "title": { "description": "Note title.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" }, "width": { "description": "Width.", "type": "integer" } }, "title": "note data", "type": "object" } }, "required": ["data"], "type": "object" } }, { "if": { "properties": { "op": { "enum": ["update", "delete"], "type": "string" } }, "required": ["op"], "type": "object" }, "then": { "properties": {}, "required": ["uuid"], "type": "object" } }], "properties": { "data": { "description": "Payload for operation.type='note'.", "properties": { "color": { "description": "Note color.", "type": "string" }, "content": { "description": "Note content.", "type": "string" }, "height": { "description": "Height.", "type": "integer" }, "icon_name": { "description": "Icon name.", "type": "string" }, "position_x": { "description": "X position.", "type": "integer" }, "position_y": { "description": "Y position.", "type": "integer" }, "title": { "description": "Note title.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" }, "width": { "description": "Width.", "type": "integer" } }, "title": "note data", "type": "object" }, "op": { "description": "Operation", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "const": "note", "description": "Must be 'note' for this payload.", "type": "string" }, "uuid": { "description": "Target UUID for update/delete.", "type": "string" } }, "required": ["op", "type"], "title": "note operation", "type": "object" }], "properties": { "data": { "description": "Type-specific payload. All field names are snake_case. For create, data.uuid may be client-supplied and referenced by later operations in the same batch.", "oneOf": [{ "description": "Payload for operation.type='table'.", "properties": { "alias": { "description": "Display alias.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "header_color": { "description": "Header color.", "type": "string" }, "name": { "description": "Table name. Required for create.", "type": "string" }, "note": { "description": "Table note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table data", "type": "object" }, { "description": "Payload for operation.type='column'.", "properties": { "default_value": { "description": "Default value.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "is_increment": { "description": "Auto-increment flag.", "type": "boolean" }, "is_not_null": { "description": "Not-null flag.", "type": "boolean" }, "is_pk": { "description": "Primary key flag.", "type": "boolean" }, "is_unique": { "description": "Unique flag.", "type": "boolean" }, "name": { "description": "Column name.", "type": "string" }, "note": { "description": "Column note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create; may be a data.uuid from an earlier table operation in this batch.", "type": "string" }, "type": { "description": "SQL data type. Use this exact field name; do not send data_type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "column data", "type": "object" }, { "description": "Payload for operation.type='index'.", "properties": { "is_pk": { "description": "Primary key index flag.", "type": "boolean" }, "is_unique": { "description": "Unique index flag.", "type": "boolean" }, "name": { "description": "Index name.", "type": "string" }, "note": { "description": "Index note.", "type": "string" }, "table_uuid": { "description": "Parent table UUID. Required for create.", "type": "string" }, "type": { "description": "Index type.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "index data", "type": "object" }, { "description": "Payload for operation.type='index_column'.", "properties": { "column_uuid": { "description": "Column UUID. Required for create.", "type": "string" }, "display_order": { "description": "Display order.", "type": "integer" }, "index_uuid": { "description": "Index UUID. Required for create.", "type": "string" }, "sort_order": { "description": "Sort order, for example ASC or DESC.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "index_column data", "type": "object" }, { "description": "Payload for operation.type='ref'.", "properties": { "from_table_uuid": { "description": "Source table UUID. Required for create.", "type": "string" }, "name": { "description": "Reference name.", "type": "string" }, "on_delete": { "description": "ON DELETE action.", "type": "string" }, "on_update": { "description": "ON UPDATE action.", "type": "string" }, "relationship": { "description": "Required for create. Use short dbdiagram-style relationship symbols.", "enum": [">", "<", "-"], "type": "string" }, "to_table_uuid": { "description": "Target table UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "ref data", "type": "object" }, { "description": "Payload for operation.type='ref_column'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "from_column_uuid": { "description": "Source column UUID. Required for create.", "type": "string" }, "ref_uuid": { "description": "Reference UUID. Required for create; may be a data.uuid from an earlier ref operation.", "type": "string" }, "to_column_uuid": { "description": "Target column UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "ref_column data", "type": "object" }, { "description": "Payload for operation.type='enum'.", "properties": { "name": { "description": "Enum name.", "type": "string" }, "note": { "description": "Enum note.", "type": "string" }, "schema_name": { "description": "Schema name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "enum data", "type": "object" }, { "description": "Payload for operation.type='enum_value'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "enum_uuid": { "description": "Parent enum UUID. Required for create; may be a data.uuid from an earlier enum operation.", "type": "string" }, "note": { "description": "Enum value note.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" }, "value": { "description": "Enum value. Inline enum values on enum are not persisted; create enum_value operations.", "type": "string" } }, "title": "enum_value data", "type": "object" }, { "description": "Payload for operation.type='table_group'.", "properties": { "color": { "description": "Group color.", "type": "string" }, "name": { "description": "Table group name.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table_group data", "type": "object" }, { "description": "Payload for operation.type='table_group_table'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "schema_name": { "description": "Table schema name.", "type": "string" }, "table_name": { "description": "Table name. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table_group_table data", "type": "object" }, { "description": "Payload for operation.type='table_group_note'.", "properties": { "display_order": { "description": "Display order.", "type": "integer" }, "group_uuid": { "description": "Table group UUID. Required for create.", "type": "string" }, "note_uuid": { "description": "Note UUID. Required for create.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "table_group_note data", "type": "object" }, { "description": "Payload for operation.type='filter'.", "properties": { "included_tables": { "description": "JSON string/list of included tables.", "type": "string" }, "is_default": { "description": "Default filter flag.", "type": "boolean" }, "name": { "description": "Filter name.", "type": "string" }, "table_groups": { "description": "JSON string/object of table groups.", "type": "string" }, "table_positions": { "description": "JSON string/object of table positions.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" } }, "title": "filter data", "type": "object" }, { "description": "Payload for operation.type='note'.", "properties": { "color": { "description": "Note color.", "type": "string" }, "content": { "description": "Note content.", "type": "string" }, "height": { "description": "Height.", "type": "integer" }, "icon_name": { "description": "Icon name.", "type": "string" }, "position_x": { "description": "X position.", "type": "integer" }, "position_y": { "description": "Y position.", "type": "integer" }, "title": { "description": "Note title.", "type": "string" }, "uuid": { "description": "Optional on create: client-generated UUID (at most 36 characters, e.g. a standard UUID v4). If supplied, later operations in the same batch can reference it. Do not put create UUIDs in operation.uuid; operation.uuid is only the update/delete target.", "maxLength": 36, "type": "string" }, "width": { "description": "Width.", "type": "integer" } }, "title": "note data", "type": "object" }], "type": "object" }, "op": { "description": "Operation. For create, put any new UUID in data.uuid; for update/delete, put target UUID in operation.uuid.", "enum": ["create", "update", "delete"], "type": "string" }, "type": { "description": "ERD entity type. This selects the operation.data shape.", "enum": ["table", "column", "index", "index_column", "ref", "ref_column", "enum", "enum_value", "table_group", "table_group_table", "table_group_note", "filter", "note"], "type": "string" }, "uuid": { "description": "Target UUID for update/delete. Do not use this for create; use data.uuid instead.", "type": "string" } }, "required": ["op", "type"], "type": "object" }, "type": "array" } }, "required": ["documentUuid", "description", "operations"], "type": "object" }, "required": ["description", "documentUuid", "operations"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "87f5bcf5a253815a" },
|
|
843
|
+
{ "mcpName": "erd_arrange_tables", "commandPath": ["erd", "arrange-tables"], "group": "erd", "description": "Arrange ERD tables after edits. Default incremental mode places newly-created tables while preserving existing positions; full=true re-layouts the entire diagram. Use after several separate table edits to tidy the canvas.", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" }, "full": { "description": "Re-layout the ENTIRE diagram (overwrites existing positions). Default false = arrange only newly-added (uncoordinated) tables to the top-right while preserving existing table positions. Call this after creating several tables to tidy them up.", "type": "boolean" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "651fd0d54e3635ff" },
|
|
844
|
+
{ "mcpName": "erd_get_enum", "commandPath": ["erd", "get-enum"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" }, "enumUuid": { "description": "Enum UUID", "type": "string" } }, "required": ["documentUuid", "enumUuid"], "type": "object" }, "required": ["documentUuid", "enumUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "f0d79d7a1996939c" },
|
|
845
|
+
{ "mcpName": "erd_get_ref", "commandPath": ["erd", "get-ref"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" }, "refUuid": { "description": "Relationship UUID", "type": "string" } }, "required": ["documentUuid", "refUuid"], "type": "object" }, "required": ["documentUuid", "refUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "5b47fe7d99e60d4d" },
|
|
846
|
+
{ "mcpName": "erd_get_table", "commandPath": ["erd", "get-table"], "group": "erd", "description": "Get one ERD table with nested columns and indexes. Returns persistent table/column/index UUIDs usable directly as erd_apply_changes update/delete targets and ref_column column references. Column fields use apply_changes snake_case names: uuid, table_uuid, name, type, is_pk, is_unique, is_not_null, is_increment, default_value, note, display_order. Index fields include uuid, name, columns (column UUIDs), is_unique, is_pk, type.", "inputSchema": { "description": "Get one ERD table with nested columns and indexes. Returns persistent table/column/index UUIDs usable directly as mcp__ainecto__erd_apply_changes update/delete targets and ref_column column references. Column fields use apply_changes snake_case names: uuid, table_uuid, name, type, is_pk, is_unique, is_not_null, is_increment, default_value, note, display_order. Index fields include uuid, name, columns (column UUIDs), is_unique, is_pk, type.", "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" }, "tableUuid": { "description": "Table UUID. Response includes columns[] and indexes[] with persistent UUIDs for apply_changes.", "type": "string" } }, "required": ["documentUuid", "tableUuid"], "type": "object" }, "required": ["documentUuid", "tableUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "0dc13648bf3fbf8f" },
|
|
847
|
+
{ "mcpName": "erd_get_table_group", "commandPath": ["erd", "get-table-group"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" }, "tableGroupUuid": { "description": "Table group UUID", "type": "string" } }, "required": ["documentUuid", "tableGroupUuid"], "type": "object" }, "required": ["documentUuid", "tableGroupUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "3db246dfe8663370" },
|
|
848
|
+
{ "mcpName": "erd_list_enums", "commandPath": ["erd", "list-enums"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "4d1f602fa487a0e4" },
|
|
849
|
+
{ "mcpName": "erd_list_refs", "commandPath": ["erd", "list-refs"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "307d766c777bbe8c" },
|
|
850
|
+
{ "mcpName": "erd_list_table_groups", "commandPath": ["erd", "list-table-groups"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "775bff53bbc21c9e" },
|
|
851
|
+
{ "mcpName": "erd_list_tables", "commandPath": ["erd", "list-tables"], "group": "erd", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "ERD document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "34324dfc7ef461c8" },
|
|
852
|
+
{ "mcpName": "get_attachment", "commandPath": ["attachment", "get"], "group": "attachment", "description": "", "inputSchema": { "properties": { "uuid": { "description": "Attachment UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "1e0e7f1bf33b669c" },
|
|
853
|
+
{ "mcpName": "get_document", "commandPath": ["document", "get"], "group": "document", "description": "Read a document. The body comes back for the types whose create/update take one: markdown \u2192 content; openapi \u2192 content (the spec text) + format/specVersion/pathCount/operationCount; html \u2192 content (the HTML text); file \u2192 file {filename, contentType, sizeBytes, viewerKind} + content for text/CSV (first 64 KB); diagram \u2192 diagramSource/sourceLanguage/edgeStyle/isModule/metadata; erd \u2192 metadata only (read tables/refs via erd_list_*).", "inputSchema": { "properties": { "uuid": { "description": "Document UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "2efef592c2d6dd7a" },
|
|
854
|
+
{ "mcpName": "get_project", "commandPath": ["project", "get"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuid": { "description": "Project UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "192403c3484ac774" },
|
|
855
|
+
{ "mcpName": "get_version", "commandPath": ["version", "get"], "group": "generic", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "Document UUID", "type": "string" }, "versionNumber": { "description": "Version number", "type": "integer" } }, "required": ["documentUuid", "versionNumber"], "type": "object" }, "required": ["documentUuid", "versionNumber"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "4e308c1a8c7203d1" },
|
|
856
|
+
{ "mcpName": "get_workspace", "commandPath": ["workspace", "get"], "group": "workspace", "description": "", "inputSchema": { "properties": { "uuid": { "description": "Workspace UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "required": ["uuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "db6d1b4879d57c56" },
|
|
857
|
+
{ "mcpName": "identity_get_self", "commandPath": ["tools", "run", "identity-get-self"], "group": "generic", "description": "Return the authenticated MCP caller identity summary; never returns email or token claims.", "inputSchema": { "additionalProperties": false, "properties": {}, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "f29c86804eb91700" },
|
|
858
|
+
{ "mcpName": "list_attachments", "commandPath": ["attachments", "list"], "group": "attachment", "description": "", "inputSchema": { "properties": { "projectUuid": { "description": "Project UUID", "type": "string" } }, "required": ["projectUuid"], "type": "object" }, "required": ["projectUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "fae7152d1e4a930b" },
|
|
859
|
+
{ "mcpName": "list_comments", "commandPath": ["comments", "list"], "group": "generic", "description": "List the active comments on a document, oldest first, including replies (a reply has parentCommentId set; replies are one level deep). Each comment has id, kind (COMMENT or QUESTION), body, author, mentions (userId, label and position in the body), and for questions questionState (UNANSWERED, ANSWERED, RESOLVED) and resolvedAt. Anyone who can read the document can list its comments.", "inputSchema": { "additionalProperties": false, "properties": { "documentUuid": { "description": "Document UUID the comments belong to", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "4c60115a00f61404" },
|
|
860
|
+
{ "mcpName": "list_documents", "commandPath": ["documents", "list"], "group": "document", "description": "", "inputSchema": { "properties": { "folderUuid": { "description": "Optional folder UUID", "type": "string" }, "limit": { "description": "Result cap when q is set (default 50)", "type": "integer" }, "projectUuid": { "description": "Project UUID", "type": "string" }, "q": { "description": "Optional full-text query. When provided, results come from the search index instead of folder listing.", "type": "string" }, "type": { "description": "Optional document type filter. Readable values: erd, markdown, diagram, openapi, html, file.", "enum": ["erd", "markdown", "diagram", "openapi", "html", "file"], "type": "string" } }, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "2171496b44dc2abf" },
|
|
861
|
+
{ "mcpName": "list_folders", "commandPath": ["folders", "list"], "group": "generic", "description": "", "inputSchema": { "properties": { "projectUuid": { "description": "Project UUID", "type": "string" } }, "required": ["projectUuid"], "type": "object" }, "required": ["projectUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "461dc6735ac92c3b" },
|
|
862
|
+
{ "mcpName": "list_mention_candidates", "commandPath": ["mention-candidates", "list"], "group": "generic", "description": "List the people you can mention in comments on a document: userId and displayName. Only people who can read the document are listed. To mention someone, write @<displayName> exactly in the comment body and pass their userId in mentionUserIds of create_comments or update_comments. Page with nextCursor while hasMore is true.", "inputSchema": { "additionalProperties": false, "properties": { "cursor": { "description": "nextCursor from the previous page (same documentUuid and query).", "type": "string" }, "documentUuid": { "description": "Document UUID the comments belong to", "type": "string" }, "limit": { "description": "Page size, 1..50. Defaults to 20.", "maximum": 50, "minimum": 1, "type": "integer" }, "query": { "description": "Name filter, 0..100 characters. Omit or leave empty for the first page of everyone.", "maxLength": 100, "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "896a5a6ff357268b" },
|
|
863
|
+
{ "mcpName": "list_projects", "commandPath": ["projects", "list"], "group": "generic", "description": "", "inputSchema": { "properties": { "limit": { "description": "Result cap when q is set (default 50)", "type": "integer" }, "q": { "description": "Optional full-text query", "type": "string" }, "workspaceUuid": { "description": "Optional Workspace UUID", "type": "string" } }, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "939bfcc088425771" },
|
|
864
|
+
{ "mcpName": "list_trash", "commandPath": ["trash", "list"], "group": "generic", "description": "", "inputSchema": { "properties": {}, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "329250aecc06a298" },
|
|
865
|
+
{ "mcpName": "list_versions", "commandPath": ["versions", "list"], "group": "generic", "description": "", "inputSchema": { "properties": { "documentUuid": { "description": "Document UUID", "type": "string" } }, "required": ["documentUuid"], "type": "object" }, "required": ["documentUuid"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "f08f9faee3ba9107" },
|
|
866
|
+
{ "mcpName": "list_workspaces", "commandPath": ["workspaces", "list"], "group": "workspace", "description": "", "inputSchema": { "properties": {}, "type": "object" }, "required": [], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "39dba0089fdbdf6a" },
|
|
867
|
+
{ "mcpName": "markdown_append_document", "commandPath": ["markdown", "append-document"], "group": "markdown", "description": "Append to Markdown documents. If the appended body is tens of KB or more, prefer request_upload_token (purpose=document.append) and upload the body with curl.", "inputSchema": { "properties": { "items": { "description": "Appends", "items": { "properties": { "addition": { "description": "Body addition. \uCD94\uAC00\uD560 \uB0B4\uC6A9\uC774 \uD06C\uBA74 (\uC218\uC2ED KB \uC774\uC0C1) LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 `request_upload_token (purpose=document.append, scopeJson={documentUuid, separator})` \uB85C uploadUrl \uC744 \uBC1B\uACE0 curl \uB85C PUT \uC73C\uB85C \uBCF4\uB0BC \uAC83.", "type": "string" }, "separator": { "description": "Separator between existing body and addition", "type": "string" }, "uuid": { "description": "Document UUID", "type": "string" } }, "required": ["uuid", "addition"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "bf82401f055f29b0" },
|
|
868
|
+
{ "mcpName": "markdown_generate_document", "commandPath": ["markdown", "generate-document"], "group": "markdown", "description": "", "inputSchema": { "properties": { "items": { "description": "Generation requests", "items": { "properties": { "context": { "description": "Optional JSON context", "type": "string" }, "documentUuid": { "description": "Target document UUID", "type": "string" }, "prompt": { "description": "Generation prompt", "type": "string" } }, "required": ["documentUuid", "prompt"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "1e4724ea42f25743" },
|
|
869
|
+
{ "mcpName": "move_documents", "commandPath": ["documents", "move"], "group": "document", "description": "", "inputSchema": { "properties": { "clientMutationId": { "description": "Optional mutation identifier for client retry tracing", "maxLength": 128, "minLength": 1, "pattern": "^[\\x21-\\x7E]{1,128}$", "type": "string" }, "items": { "description": "Moves", "items": { "properties": { "newFolderUuid": { "description": "New folder UUID \u2014 null/empty for root", "type": "string" }, "targetProjectUuid": { "description": "Target project UUID \u2014 omit to stay in current project", "type": "string" }, "targetWorkspaceUuid": { "description": "Guard only: must match targetProjectUuid workspace", "type": "string" }, "uuid": { "description": "Document UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "45899053743a0c1e" },
|
|
870
|
+
{ "mcpName": "move_folders", "commandPath": ["folders", "move"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Moves", "items": { "properties": { "newParentFolderUuid": { "description": "New parent UUID \u2014 null/empty for root", "type": "string" }, "uuid": { "description": "Folder UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "eff4458d2f35587a" },
|
|
871
|
+
{ "mcpName": "move_projects", "commandPath": ["projects", "move"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Moves", "items": { "properties": { "targetWorkspaceUuid": { "description": "Destination workspace UUID (you need write access there)", "type": "string" }, "uuid": { "description": "Project UUID", "type": "string" } }, "required": ["uuid", "targetWorkspaceUuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "abe0196da3c48ad5" },
|
|
872
|
+
{ "mcpName": "request_upload_token", "commandPath": ["tools", "run", "request-upload-token"], "group": "attachment", "description": "Use this for large payloads of tens of KB or more to save LLM tokens. purpose=mcp.call returns a short-lived bearer token that can call any MCP tool, including erd_apply_changes, via curl. purpose=document.create/update/append uploads document bodies directly to the upload endpoint with PUT/curl. Tokens are reusable for 5 minutes by default.", "inputSchema": { "properties": { "maxBytes": { "description": "\uCD5C\uB300 \uC5C5\uB85C\uB4DC byte (\uC11C\uBC84 \uCE21 \uD55C\uB3C4)", "type": "integer" }, "purpose": { "description": "Token \uC6A9\uB3C4. `mcp.call` \uC740 curl \uB85C /mcp \uD638\uCD9C, `document.create/update/append` \uB294 PUT body \uB85C \uBCF8\uBB38 \uC5C5\uB85C\uB4DC, `attachment.upload` \uB294 \uD30C\uC77C bytes \uC5C5\uB85C\uB4DC. \uCF58\uD150\uCE20\uAC00 \uD06C\uBA74 (\uC218\uC2ED KB \uC774\uC0C1) LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 \uC774 \uB3C4\uAD6C\uB85C 5\uBD84 \uC7AC\uC0AC\uC6A9 \uD1A0\uD070\uC744 \uBC1B\uACE0 curl \uB85C \uBCF4\uB0B4\uB294 \uAC83\uC744 \uAC15\uB825 \uAD8C\uC7A5.", "enum": ["mcp.call", "document.create", "document.update", "document.append", "attachment.upload"], "type": "string" }, "scopeJson": { "description": "\uC120\uD0DD \u2014 JSON scope. document.create \uB294 projectUuid, type, title \uC744 \uD3EC\uD568\uD558\uACE0 creatable type values \uB294 erd, markdown, diagram, openapi, html, file. document.update/document.append \uB294 documentUuid \uB97C \uD3EC\uD568. type=file \uC740 filename \uB3C4 \uD3EC\uD568(\uD655\uC7A5\uC790\uB85C \uD615\uC2DD \uD310\uC815) \u2014 PUT \uC758 Content-Type \uC740 \uD30C\uC77C MIME, \uBCF8\uBB38\uC740 \uD30C\uC77C \uBC14\uC774\uD2B8. attachment.upload: {documentUuid, filename}; storageKey \uB294 \uC11C\uBC84\uAC00 \uBC1C\uAE09 scope \uC5D0 \uB123\uB294\uB2E4.", "type": "string" }, "ttlSeconds": { "description": "TTL (\uCD08). \uAE30\uBCF8 300 (5\uBD84)", "type": "integer" } }, "required": ["purpose"], "type": "object" }, "required": ["purpose"], "payloadMode": "binary-upload", "destructive": false, "sourceCatalog": "prod", "schemaHash": "fe1984b3979abf86" },
|
|
873
|
+
{ "mcpName": "resolve_comments", "commandPath": ["tools", "run", "resolve-comments"], "group": "generic", "description": "Mark questions on a document as resolved. Only top-level comments with kind QUESTION can be resolved, and it needs edit access to the document (readers can comment but cannot resolve). Resolving cannot be undone. Each id is applied on its own; results report each id.", "inputSchema": { "additionalProperties": false, "properties": { "documentUuid": { "description": "Document UUID the comments belong to", "type": "string" }, "ids": { "description": "Question ids to resolve (top-level comments with kind QUESTION)", "items": { "minimum": 1, "type": "integer" }, "minItems": 1, "type": "array" } }, "required": ["documentUuid", "ids"], "type": "object" }, "required": ["documentUuid", "ids"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "7b32b72b3a0d64ba" },
|
|
874
|
+
{ "mcpName": "restore_versions", "commandPath": ["versions", "restore"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Restores", "items": { "properties": { "documentUuid": { "description": "Document UUID", "type": "string" }, "versionNumber": { "description": "Version number to restore", "type": "integer" } }, "required": ["documentUuid", "versionNumber"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "005b5b08a19fc605" },
|
|
875
|
+
{ "mcpName": "search_documents", "commandPath": ["documents", "search"], "group": "document", "description": "", "inputSchema": { "properties": { "limit": { "description": "\uACB0\uACFC \uAC1C\uC218 (\uAE30\uBCF8 20, \uCD5C\uB300 50)", "type": "integer" }, "offset": { "description": "\uD398\uC774\uC9C0\uB124\uC774\uC158 offset (\uAE30\uBCF8 0)", "type": "integer" }, "projectUuid": { "description": "\uD2B9\uC815 \uD504\uB85C\uC81D\uD2B8\uB85C \uD55C\uC815 (\uC120\uD0DD)", "type": "string" }, "q": { "description": "\uAC80\uC0C9 \uD0A4\uC6CC\uB4DC", "type": "string" }, "type": { "description": "Optional document type filter. Readable values: erd, markdown, diagram, openapi, html, file.", "enum": ["erd", "markdown", "diagram", "openapi", "html", "file"], "type": "string" } }, "required": ["q"], "type": "object" }, "required": ["q"], "payloadMode": "flags", "destructive": false, "sourceCatalog": "prod", "schemaHash": "e82abba1358754e8" },
|
|
876
|
+
{ "mcpName": "unarchive_documents", "commandPath": ["documents", "unarchive"], "group": "document", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Document UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "f2810e71583a320f" },
|
|
877
|
+
{ "mcpName": "unarchive_folders", "commandPath": ["folders", "unarchive"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Folder UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "2a24cc44696b7906" },
|
|
878
|
+
{ "mcpName": "unarchive_projects", "commandPath": ["projects", "unarchive"], "group": "generic", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Project UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "6925e444671afe19" },
|
|
879
|
+
{ "mcpName": "unarchive_workspaces", "commandPath": ["workspaces", "unarchive"], "group": "workspace", "description": "", "inputSchema": { "properties": { "uuids": { "description": "Workspace UUIDs", "items": { "type": "string" }, "type": "array" } }, "required": ["uuids"], "type": "object" }, "required": ["uuids"], "payloadMode": "file-json", "destructive": true, "sourceCatalog": "prod", "schemaHash": "8dafe9bc6378c3f2" },
|
|
880
|
+
{ "mcpName": "update_comments", "commandPath": ["comments", "update"], "group": "generic", "description": "Edit your own comments on a document. Only the author can edit, and only while they can still read the document. A resolved question and a top-level comment that already has replies are locked. Items are applied in order and each one commits on its own; results report each item.", "inputSchema": { "additionalProperties": false, "properties": { "documentUuid": { "description": "Document UUID the comments belong to", "type": "string" }, "items": { "description": "Edits, applied in order", "items": { "additionalProperties": false, "properties": { "body": { "description": "New comment text (replaces the old text).", "maxLength": 1e4, "minLength": 1, "type": "string" }, "clientMutationId": { "description": "Idempotency key (visible ASCII, at most 128 characters). Required when mentionUserIds is not empty; reuse the same value when retrying the same item so the mention email is not sent twice.", "maxLength": 128, "type": "string" }, "id": { "description": "Comment id (from mcp__ainecto__list_comments)", "minimum": 1, "type": "integer" }, "mentionUserIds": { "description": "The complete set of userIds mentioned after the edit; omit to remove all mentions. Each one's @<displayName> must appear in body. Only people who were not mentioned before get a notification email.", "items": { "type": "string" }, "maxItems": 20, "type": "array", "uniqueItems": true } }, "required": ["id", "body"], "type": "object" }, "minItems": 1, "type": "array" }, "mailLanguage": { "description": "Language of the mention notification email. Defaults to en.", "enum": ["en", "ko"], "type": "string" } }, "required": ["documentUuid", "items"], "type": "object" }, "required": ["documentUuid", "items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "fa37fedfdebd8d54" },
|
|
881
|
+
{ "mcpName": "update_documents", "commandPath": ["documents", "update"], "group": "document", "description": "Update documents. If a replacement body is tens of KB or more, prefer request_upload_token (purpose=document.update) and upload the body with curl.", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "body": { "description": "Deprecated compatibility alias for typePayload.body when document type is markdown. \uBCF8\uBB38\uC774 \uC218\uC2ED KB \uC774\uC0C1\uC73C\uB85C \uD06C\uBA74 LLM \uD1A0\uD070 \uC808\uC57D\uC744 \uC704\uD574 `request_upload_token (purpose=document.update)` \uB97C \uD638\uCD9C\uD574 uploadUrl \uC744 \uBC1B\uACE0 curl \uB85C \uC9C1\uC811 PUT \uC73C\uB85C \uBCF4\uB0BC \uAC83.", "type": "string" }, "title": { "description": "New title", "type": "string" }, "typePayload": { "description": "Type-specific payload for enabled document types. markdown: {body}; diagram: {diagramSource,sourceLanguage,edgeStyle,isModule,metadata}; openapi: {content}; html: {content} (a full HTML page); file: {file:{filename,contentType,sizeBytes,viewerKind}} (+content for text/CSV, first 64 KB).", "type": "object" }, "uuid": { "description": "Document UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "c2088f9ef5b49c6c" },
|
|
882
|
+
{ "mcpName": "update_folders", "commandPath": ["folders", "update"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "name": { "description": "New name", "type": "string" }, "uuid": { "description": "Folder UUID", "type": "string" } }, "required": ["uuid", "name"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "43c58a96ee632b5f" },
|
|
883
|
+
{ "mcpName": "update_projects", "commandPath": ["projects", "update"], "group": "generic", "description": "", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "description": { "description": "New description", "type": "string" }, "name": { "description": "New name", "type": "string" }, "uuid": { "description": "Project UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "7d849ef0a830aa8c" },
|
|
884
|
+
{ "mcpName": "update_workspaces", "commandPath": ["workspaces", "update"], "group": "workspace", "description": "", "inputSchema": { "properties": { "items": { "description": "Updates", "items": { "properties": { "description": { "description": "New description", "type": "string" }, "name": { "description": "New name", "type": "string" }, "uuid": { "description": "Workspace UUID", "type": "string" } }, "required": ["uuid"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "file-json", "destructive": false, "sourceCatalog": "prod", "schemaHash": "553275e6da799c5a" },
|
|
885
|
+
{ "mcpName": "upload_attachments", "commandPath": ["tools", "run", "upload-attachments"], "group": "attachment", "description": "", "inputSchema": { "properties": { "items": { "description": "Attachments to register", "items": { "properties": { "contentType": { "description": "MIME type", "type": "string" }, "documentUuid": { "description": "Parent document UUID", "type": "string" }, "filename": { "description": "Original file name", "type": "string" }, "sizeBytes": { "description": "Size in bytes", "type": "integer" }, "storageKey": { "description": "Storage key returned by request_upload_token purpose=attachment.upload", "type": "string" } }, "required": ["documentUuid", "filename", "storageKey"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "binary-upload", "destructive": false, "sourceCatalog": "prod", "schemaHash": "deda4a56fbbe064f" }
|
|
783
886
|
];
|
|
784
887
|
|
|
785
888
|
// src/core/catalog/generated.dev.ts
|
|
@@ -872,10 +975,147 @@ var generatedTools2 = [
|
|
|
872
975
|
{ "mcpName": "mcp__ainecto__upload_attachments", "commandPath": ["tools", "run", "upload-attachments"], "group": "attachment", "description": "", "inputSchema": { "properties": { "items": { "description": "Attachments to register", "items": { "properties": { "contentType": { "description": "MIME type", "type": "string" }, "documentUuid": { "description": "Parent document UUID", "type": "string" }, "filename": { "description": "Original file name", "type": "string" }, "sizeBytes": { "description": "Size in bytes", "type": "integer" }, "storageKey": { "description": "Storage key returned by request_upload_token purpose=attachment.upload", "type": "string" } }, "required": ["documentUuid", "filename", "storageKey"], "type": "object" }, "type": "array" } }, "required": ["items"], "type": "object" }, "required": ["items"], "payloadMode": "binary-upload", "destructive": false, "sourceCatalog": "dev", "schemaHash": "0622e590de3404dd" }
|
|
873
976
|
];
|
|
874
977
|
|
|
978
|
+
// src/core/catalog/generator.ts
|
|
979
|
+
import { createHash as createHash3 } from "crypto";
|
|
980
|
+
var LEGACY_PREFIX = "mcp__ainecto__";
|
|
981
|
+
function bareToolName(name) {
|
|
982
|
+
return name.startsWith(LEGACY_PREFIX) ? name.slice(LEGACY_PREFIX.length) : name;
|
|
983
|
+
}
|
|
984
|
+
var DESTRUCTIVE_PREFIXES = [
|
|
985
|
+
"delete_",
|
|
986
|
+
"remove_",
|
|
987
|
+
"archive_",
|
|
988
|
+
"unarchive_",
|
|
989
|
+
"restore_",
|
|
990
|
+
"move_",
|
|
991
|
+
"erd_delete_",
|
|
992
|
+
"erd_remove_",
|
|
993
|
+
"task_delete_",
|
|
994
|
+
"testcase_delete_"
|
|
995
|
+
];
|
|
996
|
+
function generateCatalog(tools, sourceCatalog) {
|
|
997
|
+
return tools.map((tool) => normalizeTool(tool, sourceCatalog)).sort((a, b) => a.mcpName.localeCompare(b.mcpName));
|
|
998
|
+
}
|
|
999
|
+
function normalizeTool(tool, sourceCatalog) {
|
|
1000
|
+
if (!tool.name) {
|
|
1001
|
+
throw new Error("Unsupported tool name: empty.");
|
|
1002
|
+
}
|
|
1003
|
+
const stripped = bareToolName(tool.name);
|
|
1004
|
+
const group = inferGroup(stripped);
|
|
1005
|
+
const inputSchema = normalizeSchema(tool.inputSchema ?? { type: "object", properties: {} });
|
|
1006
|
+
const required = Array.isArray(inputSchema.required) ? inputSchema.required.filter((value) => typeof value === "string").sort() : [];
|
|
1007
|
+
const definition = {
|
|
1008
|
+
mcpName: tool.name,
|
|
1009
|
+
commandPath: generateCommandPath(stripped, group),
|
|
1010
|
+
group,
|
|
1011
|
+
description: tool.description ?? "",
|
|
1012
|
+
inputSchema,
|
|
1013
|
+
required,
|
|
1014
|
+
payloadMode: inferPayloadMode(stripped, inputSchema),
|
|
1015
|
+
destructive: inferDestructive(stripped, tool),
|
|
1016
|
+
sourceCatalog,
|
|
1017
|
+
schemaHash: ""
|
|
1018
|
+
};
|
|
1019
|
+
return { ...definition, schemaHash: computeSchemaHash(definition) };
|
|
1020
|
+
}
|
|
1021
|
+
function inferGroup(strippedName) {
|
|
1022
|
+
if (strippedName.startsWith("erd_")) return "erd";
|
|
1023
|
+
if (strippedName.startsWith("flow_")) return "flow";
|
|
1024
|
+
if (strippedName.startsWith("testcase_")) return "testcase";
|
|
1025
|
+
if (strippedName.startsWith("markdown_")) return "markdown";
|
|
1026
|
+
if (strippedName.includes("attachment") || strippedName.includes("upload")) return "attachment";
|
|
1027
|
+
if (strippedName.startsWith("task_")) return "task";
|
|
1028
|
+
if (strippedName.includes("workspace")) return "workspace";
|
|
1029
|
+
if (strippedName.includes("document")) return "document";
|
|
1030
|
+
return "generic";
|
|
1031
|
+
}
|
|
1032
|
+
function generateCommandPath(strippedName, group) {
|
|
1033
|
+
if (group !== "generic" && strippedName.startsWith(`${group}_`)) {
|
|
1034
|
+
return [group, snakeToKebab(strippedName.slice(group.length + 1))];
|
|
1035
|
+
}
|
|
1036
|
+
const [verb, ...rest] = strippedName.split("_");
|
|
1037
|
+
if (verb && rest.length > 0 && isVerb(verb)) {
|
|
1038
|
+
return [rest.join("-"), verb];
|
|
1039
|
+
}
|
|
1040
|
+
return ["tools", "run", snakeToKebab(strippedName)];
|
|
1041
|
+
}
|
|
1042
|
+
function inferPayloadMode(strippedName, inputSchema) {
|
|
1043
|
+
if (strippedName.includes("upload") || strippedName.includes("attachment_upload")) {
|
|
1044
|
+
return "binary-upload";
|
|
1045
|
+
}
|
|
1046
|
+
const properties = inputSchema.properties;
|
|
1047
|
+
if (!isRecord3(properties)) {
|
|
1048
|
+
return "flags";
|
|
1049
|
+
}
|
|
1050
|
+
const values = Object.values(properties);
|
|
1051
|
+
if (values.length === 0) {
|
|
1052
|
+
return "flags";
|
|
1053
|
+
}
|
|
1054
|
+
if (values.every(isScalarSchema)) {
|
|
1055
|
+
return "flags";
|
|
1056
|
+
}
|
|
1057
|
+
return "file-json";
|
|
1058
|
+
}
|
|
1059
|
+
function inferDestructive(strippedName, tool) {
|
|
1060
|
+
if (tool.annotations?.destructiveHint === true) {
|
|
1061
|
+
return true;
|
|
1062
|
+
}
|
|
1063
|
+
return DESTRUCTIVE_PREFIXES.some((prefix) => strippedName.startsWith(prefix));
|
|
1064
|
+
}
|
|
1065
|
+
function computeSchemaHash(tool) {
|
|
1066
|
+
return createHash3("sha256").update(stableStringify({
|
|
1067
|
+
name: tool.mcpName,
|
|
1068
|
+
description: tool.description,
|
|
1069
|
+
inputSchema: tool.inputSchema
|
|
1070
|
+
})).digest("hex").slice(0, 16);
|
|
1071
|
+
}
|
|
1072
|
+
function stableStringify(value) {
|
|
1073
|
+
if (Array.isArray(value)) {
|
|
1074
|
+
return `[${value.map(stableStringify).join(",")}]`;
|
|
1075
|
+
}
|
|
1076
|
+
if (isRecord3(value)) {
|
|
1077
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",")}}`;
|
|
1078
|
+
}
|
|
1079
|
+
return JSON.stringify(value);
|
|
1080
|
+
}
|
|
1081
|
+
function normalizeSchema(schema) {
|
|
1082
|
+
return JSON.parse(stableStringify(schema));
|
|
1083
|
+
}
|
|
1084
|
+
function isScalarSchema(value) {
|
|
1085
|
+
if (!isRecord3(value)) {
|
|
1086
|
+
return false;
|
|
1087
|
+
}
|
|
1088
|
+
const type = value.type;
|
|
1089
|
+
return type === "string" || type === "number" || type === "integer" || type === "boolean";
|
|
1090
|
+
}
|
|
1091
|
+
function isVerb(value) {
|
|
1092
|
+
return [
|
|
1093
|
+
"list",
|
|
1094
|
+
"get",
|
|
1095
|
+
"create",
|
|
1096
|
+
"update",
|
|
1097
|
+
"delete",
|
|
1098
|
+
"move",
|
|
1099
|
+
"restore",
|
|
1100
|
+
"archive",
|
|
1101
|
+
"unarchive",
|
|
1102
|
+
"search"
|
|
1103
|
+
].includes(value);
|
|
1104
|
+
}
|
|
1105
|
+
function snakeToKebab(value) {
|
|
1106
|
+
return value.replaceAll("_", "-");
|
|
1107
|
+
}
|
|
1108
|
+
function isRecord3(value) {
|
|
1109
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
875
1112
|
// src/core/catalog/index.ts
|
|
876
1113
|
function getGeneratedTools(env) {
|
|
877
1114
|
return env === "dev" ? generatedTools2 : generatedTools;
|
|
878
1115
|
}
|
|
1116
|
+
async function loadLiveTools(env, client) {
|
|
1117
|
+
return generateCatalog(await client.toolsList(), env);
|
|
1118
|
+
}
|
|
879
1119
|
|
|
880
1120
|
// src/core/mcp/stdioBridge.ts
|
|
881
1121
|
import { createInterface } from "readline";
|
|
@@ -905,7 +1145,7 @@ async function runStdioBridge(options) {
|
|
|
905
1145
|
}
|
|
906
1146
|
function parseJsonRpcRequest(line) {
|
|
907
1147
|
const parsed = JSON.parse(line);
|
|
908
|
-
if (!
|
|
1148
|
+
if (!isRecord4(parsed) || parsed.jsonrpc !== "2.0" || typeof parsed.method !== "string") {
|
|
909
1149
|
throw new Error("Invalid JSON-RPC request.");
|
|
910
1150
|
}
|
|
911
1151
|
return {
|
|
@@ -915,14 +1155,13 @@ function parseJsonRpcRequest(line) {
|
|
|
915
1155
|
params: parsed.params
|
|
916
1156
|
};
|
|
917
1157
|
}
|
|
918
|
-
function
|
|
1158
|
+
function isRecord4(value) {
|
|
919
1159
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
920
1160
|
}
|
|
921
1161
|
|
|
922
1162
|
// src/adapters/mcp/connector.ts
|
|
923
1163
|
async function runConnector(options) {
|
|
924
|
-
const
|
|
925
|
-
const auth = new OAuthClient({ endpoint: options.endpoint, tokenStore, role: options.role });
|
|
1164
|
+
const auth = new OAuthClient({ endpoint: options.endpoint, tokenStore: new FileTokenStore() });
|
|
926
1165
|
const client = new McpRpcClient({
|
|
927
1166
|
endpoint: options.endpoint,
|
|
928
1167
|
tokenProvider: auth,
|
|
@@ -939,85 +1178,85 @@ async function runConnector(options) {
|
|
|
939
1178
|
// src/core/catalog/enrichments.ts
|
|
940
1179
|
var enrichments = [
|
|
941
1180
|
{
|
|
942
|
-
mcpName: "
|
|
1181
|
+
mcpName: "list_projects",
|
|
943
1182
|
outputHint: "table",
|
|
944
1183
|
tableColumns: ["uuid", "name", "projectKey", "workspaceUuid", "updatedAt"],
|
|
945
1184
|
displayName: "List projects",
|
|
946
1185
|
examples: ["ainecto projects list --env dev"]
|
|
947
1186
|
},
|
|
948
1187
|
{
|
|
949
|
-
mcpName: "
|
|
1188
|
+
mcpName: "list_workspaces",
|
|
950
1189
|
outputHint: "table",
|
|
951
1190
|
tableColumns: ["uuid", "name", "updatedAt"],
|
|
952
1191
|
displayName: "List workspaces"
|
|
953
1192
|
},
|
|
954
1193
|
{
|
|
955
|
-
mcpName: "
|
|
1194
|
+
mcpName: "list_folders",
|
|
956
1195
|
outputHint: "table",
|
|
957
1196
|
tableColumns: ["uuid", "name", "projectUuid", "parentFolderUuid", "updatedAt"],
|
|
958
1197
|
displayName: "List folders"
|
|
959
1198
|
},
|
|
960
1199
|
{
|
|
961
|
-
mcpName: "
|
|
1200
|
+
mcpName: "list_documents",
|
|
962
1201
|
outputHint: "table",
|
|
963
1202
|
tableColumns: ["uuid", "title", "type", "projectUuid", "folderUuid", "updatedAt"],
|
|
964
1203
|
displayName: "List documents"
|
|
965
1204
|
},
|
|
966
1205
|
{
|
|
967
|
-
mcpName: "
|
|
1206
|
+
mcpName: "search_documents",
|
|
968
1207
|
outputHint: "table",
|
|
969
1208
|
tableColumns: ["uuid", "title", "type", "projectUuid", "updatedAt"],
|
|
970
1209
|
displayName: "Search documents"
|
|
971
1210
|
},
|
|
972
1211
|
{
|
|
973
|
-
mcpName: "
|
|
1212
|
+
mcpName: "list_attachments",
|
|
974
1213
|
outputHint: "table",
|
|
975
1214
|
tableColumns: ["uuid", "filename", "contentType", "sizeBytes", "documentUuid"],
|
|
976
1215
|
displayName: "List attachments"
|
|
977
1216
|
},
|
|
978
1217
|
{
|
|
979
|
-
mcpName: "
|
|
1218
|
+
mcpName: "erd_list_tables",
|
|
980
1219
|
outputHint: "table",
|
|
981
1220
|
tableColumns: ["uuid", "name", "schema_name", "display_order"],
|
|
982
1221
|
displayName: "List ERD tables"
|
|
983
1222
|
},
|
|
984
1223
|
{
|
|
985
|
-
mcpName: "
|
|
1224
|
+
mcpName: "erd_list_refs",
|
|
986
1225
|
outputHint: "table",
|
|
987
1226
|
tableColumns: ["uuid", "name", "from_table_uuid", "to_table_uuid", "relationship"],
|
|
988
1227
|
displayName: "List ERD refs"
|
|
989
1228
|
},
|
|
990
1229
|
{
|
|
991
|
-
mcpName: "
|
|
1230
|
+
mcpName: "erd_list_enums",
|
|
992
1231
|
outputHint: "table",
|
|
993
1232
|
tableColumns: ["uuid", "name", "display_order"],
|
|
994
1233
|
displayName: "List ERD enums"
|
|
995
1234
|
},
|
|
996
1235
|
{
|
|
997
|
-
mcpName: "
|
|
1236
|
+
mcpName: "erd_list_table_groups",
|
|
998
1237
|
outputHint: "table",
|
|
999
1238
|
tableColumns: ["uuid", "name", "display_order"],
|
|
1000
1239
|
displayName: "List ERD table groups"
|
|
1001
1240
|
},
|
|
1002
1241
|
{
|
|
1003
|
-
mcpName: "
|
|
1242
|
+
mcpName: "task_list_tasks",
|
|
1004
1243
|
outputHint: "table",
|
|
1005
1244
|
tableColumns: ["uuid", "title", "statusUuid", "assigneeId", "updatedAt"],
|
|
1006
1245
|
displayName: "List tasks"
|
|
1007
1246
|
},
|
|
1008
1247
|
{
|
|
1009
|
-
mcpName: "
|
|
1248
|
+
mcpName: "task_list_statuses",
|
|
1010
1249
|
outputHint: "table",
|
|
1011
1250
|
tableColumns: ["uuid", "name", "displayOrder"],
|
|
1012
1251
|
displayName: "List task statuses"
|
|
1013
1252
|
},
|
|
1014
1253
|
{
|
|
1015
|
-
mcpName: "
|
|
1254
|
+
mcpName: "enable_shares",
|
|
1016
1255
|
aliases: ["shares enable"],
|
|
1017
1256
|
displayName: "Enable shares"
|
|
1018
1257
|
},
|
|
1019
1258
|
{
|
|
1020
|
-
mcpName: "
|
|
1259
|
+
mcpName: "disable_shares",
|
|
1021
1260
|
aliases: ["shares disable"],
|
|
1022
1261
|
displayName: "Disable shares"
|
|
1023
1262
|
}
|
|
@@ -1026,7 +1265,7 @@ var enrichments = [
|
|
|
1026
1265
|
// src/core/output/table.ts
|
|
1027
1266
|
function renderTable(data, options = {}) {
|
|
1028
1267
|
const rows = extractRows(data);
|
|
1029
|
-
if (!rows.length || !rows.every(
|
|
1268
|
+
if (!rows.length || !rows.every(isRecord5)) {
|
|
1030
1269
|
return void 0;
|
|
1031
1270
|
}
|
|
1032
1271
|
const columns = options.columns?.length ? options.columns : inferColumns(rows);
|
|
@@ -1043,7 +1282,7 @@ function extractRows(data) {
|
|
|
1043
1282
|
if (Array.isArray(data)) {
|
|
1044
1283
|
return data;
|
|
1045
1284
|
}
|
|
1046
|
-
if (
|
|
1285
|
+
if (isRecord5(data)) {
|
|
1047
1286
|
for (const key of ["items", "projects", "workspaces", "folders", "documents", "tasks", "attachments", "results", "data"]) {
|
|
1048
1287
|
const value = data[key];
|
|
1049
1288
|
if (Array.isArray(value)) {
|
|
@@ -1071,7 +1310,7 @@ function formatCell(value) {
|
|
|
1071
1310
|
}
|
|
1072
1311
|
return String(value);
|
|
1073
1312
|
}
|
|
1074
|
-
function
|
|
1313
|
+
function isRecord5(value) {
|
|
1075
1314
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1076
1315
|
}
|
|
1077
1316
|
|
|
@@ -1124,7 +1363,7 @@ function payloadToArgs(value) {
|
|
|
1124
1363
|
if (value === void 0 || value === null) {
|
|
1125
1364
|
return {};
|
|
1126
1365
|
}
|
|
1127
|
-
if (!
|
|
1366
|
+
if (!isRecord6(value)) {
|
|
1128
1367
|
throw new CliCommandError("INVALID_PAYLOAD", "Generated command payload must be a JSON object.", { value });
|
|
1129
1368
|
}
|
|
1130
1369
|
return { ...value };
|
|
@@ -1228,12 +1467,12 @@ function coerceFlagValue(value, schema, key) {
|
|
|
1228
1467
|
}
|
|
1229
1468
|
function getSchemaProperties(schema) {
|
|
1230
1469
|
const properties = schema.properties;
|
|
1231
|
-
if (!
|
|
1470
|
+
if (!isRecord6(properties)) {
|
|
1232
1471
|
return {};
|
|
1233
1472
|
}
|
|
1234
1473
|
const result = {};
|
|
1235
1474
|
for (const [key, value] of Object.entries(properties)) {
|
|
1236
|
-
if (
|
|
1475
|
+
if (isRecord6(value)) {
|
|
1237
1476
|
result[key] = value;
|
|
1238
1477
|
}
|
|
1239
1478
|
}
|
|
@@ -1248,7 +1487,7 @@ function requireValue(flag, value) {
|
|
|
1248
1487
|
}
|
|
1249
1488
|
return value;
|
|
1250
1489
|
}
|
|
1251
|
-
function
|
|
1490
|
+
function isRecord6(value) {
|
|
1252
1491
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1253
1492
|
}
|
|
1254
1493
|
|
|
@@ -1290,9 +1529,8 @@ async function promptConfirmation(io, message) {
|
|
|
1290
1529
|
var ATTACHMENTS_UPLOAD_COMMAND_PATH = ["attachments", "upload"];
|
|
1291
1530
|
|
|
1292
1531
|
// src/adapters/cli/generatedCommandRouter.ts
|
|
1293
|
-
function matchGeneratedCommand(
|
|
1532
|
+
function matchGeneratedCommand(tools, argv) {
|
|
1294
1533
|
const commandTokens = argv.filter((arg) => !arg.startsWith("-"));
|
|
1295
|
-
const tools = getGeneratedTools(env);
|
|
1296
1534
|
const entries = buildRouteEntries(tools, enrichments);
|
|
1297
1535
|
const match = entries.filter((entry) => isPrefix(entry.path, commandTokens)).sort((a, b) => b.path.length - a.path.length)[0];
|
|
1298
1536
|
if (!match) {
|
|
@@ -1308,7 +1546,7 @@ function matchGeneratedCommand(env, argv) {
|
|
|
1308
1546
|
};
|
|
1309
1547
|
}
|
|
1310
1548
|
async function executeGeneratedCommand(options) {
|
|
1311
|
-
const route = matchGeneratedCommand(options.
|
|
1549
|
+
const route = matchGeneratedCommand(options.tools, options.argv);
|
|
1312
1550
|
if (!route) {
|
|
1313
1551
|
return 2;
|
|
1314
1552
|
}
|
|
@@ -1471,7 +1709,7 @@ async function requestUploadToken(client, metadata, context) {
|
|
|
1471
1709
|
requestArgs.ttlSeconds = context.ttlSeconds;
|
|
1472
1710
|
}
|
|
1473
1711
|
const response = unwrapMcpToolPayload(await client.toolsCall(context.requestToolName, requestArgs));
|
|
1474
|
-
if (!
|
|
1712
|
+
if (!isRecord7(response)) {
|
|
1475
1713
|
throw new AttachmentUploadError("UPLOAD_TOKEN_PROTOCOL_ERROR", "request_upload_token response was not an object.");
|
|
1476
1714
|
}
|
|
1477
1715
|
const token = response.token;
|
|
@@ -1541,7 +1779,7 @@ function parsePutResponse(text) {
|
|
|
1541
1779
|
} catch {
|
|
1542
1780
|
throw new AttachmentUploadError("ATTACHMENT_UPLOAD_PROTOCOL_ERROR", "Attachment PUT response was not valid JSON.");
|
|
1543
1781
|
}
|
|
1544
|
-
if (!
|
|
1782
|
+
if (!isRecord7(parsed)) {
|
|
1545
1783
|
throw new AttachmentUploadError("ATTACHMENT_UPLOAD_PROTOCOL_ERROR", "Attachment PUT response was not an object.");
|
|
1546
1784
|
}
|
|
1547
1785
|
if (typeof parsed.storageKey !== "string") {
|
|
@@ -1574,10 +1812,10 @@ function validatePutResult(token, metadata, putResult) {
|
|
|
1574
1812
|
}
|
|
1575
1813
|
}
|
|
1576
1814
|
function unwrapMcpToolPayload(value) {
|
|
1577
|
-
if (!
|
|
1815
|
+
if (!isRecord7(value) || !Array.isArray(value.content)) {
|
|
1578
1816
|
return value;
|
|
1579
1817
|
}
|
|
1580
|
-
const text = value.content.map((item) =>
|
|
1818
|
+
const text = value.content.map((item) => isRecord7(item) && typeof item.text === "string" ? item.text : "").find((itemText) => itemText.trim().startsWith("{"));
|
|
1581
1819
|
if (!text) {
|
|
1582
1820
|
return value;
|
|
1583
1821
|
}
|
|
@@ -1619,7 +1857,7 @@ var MIME_BY_EXTENSION = {
|
|
|
1619
1857
|
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1620
1858
|
".zip": "application/zip"
|
|
1621
1859
|
};
|
|
1622
|
-
function
|
|
1860
|
+
function isRecord7(value) {
|
|
1623
1861
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1624
1862
|
}
|
|
1625
1863
|
|
|
@@ -1744,12 +1982,12 @@ function renderAttachmentUploadResult(result, json) {
|
|
|
1744
1982
|
}
|
|
1745
1983
|
|
|
1746
1984
|
// src/adapters/cli/initCommand.ts
|
|
1747
|
-
import { chmod as chmod2,
|
|
1748
|
-
import { dirname as
|
|
1985
|
+
import { chmod as chmod2, mkdir as mkdir2, rename as rename2, rm as rm2, rmdir, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
1986
|
+
import { dirname as dirname3, join as join3, relative, resolve as resolve2, sep as sep2 } from "path";
|
|
1749
1987
|
|
|
1750
1988
|
// src/core/harness/agentTargets.ts
|
|
1751
|
-
|
|
1752
|
-
var
|
|
1989
|
+
import { basename as basename2 } from "path";
|
|
1990
|
+
var SERVER_NAME = "ainecto";
|
|
1753
1991
|
var AGENT_TARGETS = [
|
|
1754
1992
|
{ id: "claude-code", label: "Claude Code", path: ".mcp.json" },
|
|
1755
1993
|
{ id: "cursor", label: "Cursor", path: ".cursor/mcp.json" }
|
|
@@ -1765,7 +2003,7 @@ function buildServerEntry(input) {
|
|
|
1765
2003
|
return { command: "npx", args };
|
|
1766
2004
|
}
|
|
1767
2005
|
function readRoleFromEntry(entry) {
|
|
1768
|
-
if (!
|
|
2006
|
+
if (!isRecord8(entry) || !Array.isArray(entry.args)) {
|
|
1769
2007
|
return void 0;
|
|
1770
2008
|
}
|
|
1771
2009
|
const index = entry.args.indexOf("--role");
|
|
@@ -1778,11 +2016,26 @@ function readServerEntry(existing) {
|
|
|
1778
2016
|
}
|
|
1779
2017
|
try {
|
|
1780
2018
|
const parsed = JSON.parse(existing);
|
|
1781
|
-
|
|
2019
|
+
const servers = parsed?.mcpServers;
|
|
2020
|
+
return isRecord8(servers) ? servers[SERVER_NAME] : void 0;
|
|
1782
2021
|
} catch {
|
|
1783
2022
|
return void 0;
|
|
1784
2023
|
}
|
|
1785
2024
|
}
|
|
2025
|
+
function isOurBridge(entry) {
|
|
2026
|
+
if (!isRecord8(entry)) {
|
|
2027
|
+
return false;
|
|
2028
|
+
}
|
|
2029
|
+
const { command, args } = entry;
|
|
2030
|
+
const argList = Array.isArray(args) ? args.filter((arg) => typeof arg === "string") : [];
|
|
2031
|
+
const isOurPackage = (arg) => arg === PACKAGE_NAME || arg.startsWith(`${PACKAGE_NAME}@`);
|
|
2032
|
+
if (argList.some((arg) => isOurPackage(arg) || arg === "--role")) {
|
|
2033
|
+
return true;
|
|
2034
|
+
}
|
|
2035
|
+
const bin = typeof command === "string" ? basename2(command) : "";
|
|
2036
|
+
return OUR_BINS.includes(bin);
|
|
2037
|
+
}
|
|
2038
|
+
var OUR_BINS = ["ainecto"];
|
|
1786
2039
|
function mergeServerEntry(existing, entry) {
|
|
1787
2040
|
const root = parseConfigObject(existing);
|
|
1788
2041
|
const servers = readServers(root);
|
|
@@ -1802,7 +2055,9 @@ function removeServerEntry(existing, restore) {
|
|
|
1802
2055
|
servers[SERVER_NAME] = restore.value;
|
|
1803
2056
|
return { content: stringify({ ...root, mcpServers: servers }), removed, emptied: false };
|
|
1804
2057
|
}
|
|
1805
|
-
|
|
2058
|
+
if (removed) {
|
|
2059
|
+
delete servers[SERVER_NAME];
|
|
2060
|
+
}
|
|
1806
2061
|
const otherKeys = Object.keys(root).filter((key) => key !== "mcpServers");
|
|
1807
2062
|
const emptied = Object.keys(servers).length === 0 && otherKeys.length === 0;
|
|
1808
2063
|
return { content: stringify({ ...root, mcpServers: servers }), removed, emptied };
|
|
@@ -1832,7 +2087,7 @@ function readServers(root) {
|
|
|
1832
2087
|
if (servers === void 0) {
|
|
1833
2088
|
return {};
|
|
1834
2089
|
}
|
|
1835
|
-
if (!
|
|
2090
|
+
if (!isRecord8(servers)) {
|
|
1836
2091
|
throw new Error(
|
|
1837
2092
|
'Existing MCP config has an "mcpServers" value that is not an object, so it was left untouched. Fix it by hand and run init again.'
|
|
1838
2093
|
);
|
|
@@ -1851,7 +2106,7 @@ function parseConfigObject(existing) {
|
|
|
1851
2106
|
`Existing MCP config is not valid JSON, so it was left untouched. Fix it by hand and run init again. (${error instanceof Error ? error.message : String(error)})`
|
|
1852
2107
|
);
|
|
1853
2108
|
}
|
|
1854
|
-
if (!
|
|
2109
|
+
if (!isRecord8(parsed)) {
|
|
1855
2110
|
throw new Error("Existing MCP config must be a JSON object; it was left untouched.");
|
|
1856
2111
|
}
|
|
1857
2112
|
return parsed;
|
|
@@ -1860,16 +2115,18 @@ function stringify(value) {
|
|
|
1860
2115
|
return `${JSON.stringify(value, null, 2)}
|
|
1861
2116
|
`;
|
|
1862
2117
|
}
|
|
1863
|
-
function
|
|
2118
|
+
function isRecord8(value) {
|
|
1864
2119
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1865
2120
|
}
|
|
1866
2121
|
|
|
1867
2122
|
// src/core/harness/harnessDoc.ts
|
|
1868
|
-
var MARKER_BEGIN = "<!--
|
|
1869
|
-
var MARKER_END = "<!--
|
|
1870
|
-
var
|
|
1871
|
-
var
|
|
1872
|
-
var
|
|
2123
|
+
var MARKER_BEGIN = "<!-- ainecto:begin -->";
|
|
2124
|
+
var MARKER_END = "<!-- ainecto:end -->";
|
|
2125
|
+
var MARKERS = { begin: MARKER_BEGIN, end: MARKER_END };
|
|
2126
|
+
var HARNESS_DIR = ".ainecto";
|
|
2127
|
+
var HARNESS_DOC_PATH = `${HARNESS_DIR}/HARNESS.md`;
|
|
2128
|
+
var HARNESS_CONFIG_PATH = `${HARNESS_DIR}/config.json`;
|
|
2129
|
+
var HARNESS_RECORD_PATH = `${HARNESS_DIR}/init-record.json`;
|
|
1873
2130
|
function renderHarnessDoc(input, template) {
|
|
1874
2131
|
return fillTemplate(template ?? DEFAULT_HARNESS_DOC, {
|
|
1875
2132
|
projectName: input.projectName,
|
|
@@ -1897,9 +2154,9 @@ function documentContractProblem(key, body) {
|
|
|
1897
2154
|
}
|
|
1898
2155
|
return void 0;
|
|
1899
2156
|
}
|
|
1900
|
-
var DEFAULT_HARNESS_DOC = `#
|
|
2157
|
+
var DEFAULT_HARNESS_DOC = `# AiNecto Harness
|
|
1901
2158
|
|
|
1902
|
-
This repository is wired to an
|
|
2159
|
+
This repository is wired to an AiNecto project. The design source of truth lives in AiNecto,
|
|
1903
2160
|
not in this repository's files.
|
|
1904
2161
|
|
|
1905
2162
|
- Project: **{{projectName}}** (\`{{projectUuid}}\`)
|
|
@@ -1908,9 +2165,11 @@ not in this repository's files.
|
|
|
1908
2165
|
## One session, one role
|
|
1909
2166
|
|
|
1910
2167
|
A session picks exactly one role and keeps it for the whole session. The role comes from the
|
|
1911
|
-
MCP server configuration \u2014 not from this file
|
|
1912
|
-
|
|
1913
|
-
|
|
2168
|
+
MCP server configuration \u2014 not from this file \u2014 and it does not change during a session. The
|
|
2169
|
+
user picks the role; an agent applies a new one only when the user asks, and it takes effect in
|
|
2170
|
+
the next session. This is a working guardrail, not a security boundary. The server exposes only
|
|
2171
|
+
the tools that role is allowed to use, so a tool that is out of role is not merely discouraged:
|
|
2172
|
+
it is absent.
|
|
1914
2173
|
|
|
1915
2174
|
| Role | Owns | Must not |
|
|
1916
2175
|
| --- | --- | --- |
|
|
@@ -1937,7 +2196,7 @@ and which session should pick it up next.
|
|
|
1937
2196
|
|
|
1938
2197
|
## Not every change needs four sessions
|
|
1939
2198
|
|
|
1940
|
-
A small change that fits the design already recorded in
|
|
2199
|
+
A small change that fits the design already recorded in AiNecto starts at Development. The rule
|
|
1941
2200
|
is not "always run four steps" \u2014 it is that when a step is needed, it does not happen inside
|
|
1942
2201
|
another role's session.
|
|
1943
2202
|
|
|
@@ -1948,10 +2207,10 @@ Change the role in the MCP server configuration and start a new session:
|
|
|
1948
2207
|
${AGENT_TARGETS.map((target) => ` ${target.label.padEnd(12)} ${target.path}`).join("\n")}
|
|
1949
2208
|
Codex global config \u2014 use a profile: codex -p <role>, or set ${ROLE_ENV_VAR}
|
|
1950
2209
|
|
|
1951
|
-
Or run \`
|
|
2210
|
+
Or run \`ainecto init --role <design|development|test|validation>\` to rewrite them.
|
|
1952
2211
|
|
|
1953
2212
|
---
|
|
1954
|
-
Generated by \`
|
|
2213
|
+
Generated by \`ainecto init\`. Remove everything it wrote with \`ainecto init --undo\`.
|
|
1955
2214
|
`;
|
|
1956
2215
|
function renderAgentNote(template) {
|
|
1957
2216
|
return fillTemplate(template ?? DEFAULT_AGENT_NOTE, {
|
|
@@ -1960,15 +2219,15 @@ function renderAgentNote(template) {
|
|
|
1960
2219
|
});
|
|
1961
2220
|
}
|
|
1962
2221
|
var DEFAULT_AGENT_NOTE = `${MARKER_BEGIN}
|
|
1963
|
-
##
|
|
2222
|
+
## AiNecto Harness
|
|
1964
2223
|
|
|
1965
|
-
This repository uses
|
|
2224
|
+
This repository uses AiNecto as its design source of truth, and each session has one role
|
|
1966
2225
|
(Design / Development / Test / Validation). Read \`{{harnessDocPath}}\` before doing work that
|
|
1967
2226
|
touches requirements, the ERD, or architecture.
|
|
1968
2227
|
|
|
1969
|
-
The role of this session comes from the \`{{serverName}}\` MCP server configuration
|
|
1970
|
-
|
|
1971
|
-
|
|
2228
|
+
The role of this session comes from the \`{{serverName}}\` MCP server configuration that
|
|
2229
|
+
\`ainecto init\` wrote; the server states it in its \`initialize\` instructions and exposes only
|
|
2230
|
+
that role's tools. If the work needs something outside the role,
|
|
1972
2231
|
stop and report which session is required.
|
|
1973
2232
|
${MARKER_END}`;
|
|
1974
2233
|
function managedBlockProblem(key, body) {
|
|
@@ -2020,38 +2279,47 @@ function upsertMarkerBlock(existing, block) {
|
|
|
2020
2279
|
return composed;
|
|
2021
2280
|
}
|
|
2022
2281
|
function assertComposedBlockIsOurs(composed, existing) {
|
|
2023
|
-
if (ownedMarkerCount(composed,
|
|
2282
|
+
if (ownedMarkerCount(composed, "begin") === 1 && ownedMarkerCount(composed, "end") === 1) {
|
|
2024
2283
|
return;
|
|
2025
2284
|
}
|
|
2026
2285
|
const reason = hasUnclosedFence(existing) ? "this file ends inside an unclosed code fence, so the block would sit inside it" : "the block could not be found again once merged into this file";
|
|
2027
2286
|
throw new Error(
|
|
2028
|
-
`
|
|
2287
|
+
`ainecto could not place its block where --undo would find it again \u2014 ${reason}. The file was left untouched; fix that and run again.`
|
|
2029
2288
|
);
|
|
2030
2289
|
}
|
|
2031
2290
|
function assertNoDanglingMarker(content) {
|
|
2032
|
-
const begins = ownedMarkerCount(content,
|
|
2033
|
-
const ends = ownedMarkerCount(content,
|
|
2291
|
+
const begins = ownedMarkerCount(content, "begin");
|
|
2292
|
+
const ends = ownedMarkerCount(content, "end");
|
|
2034
2293
|
if (begins !== ends || begins > 1) {
|
|
2035
2294
|
throw new Error(
|
|
2036
|
-
`
|
|
2295
|
+
`AiNecto markers are unbalanced (${begins} begin, ${ends} end). The file was left untouched \u2014 remove the stray ${MARKER_BEGIN} / ${MARKER_END} by hand and run again.`
|
|
2037
2296
|
);
|
|
2038
2297
|
}
|
|
2039
2298
|
if (begins === 0) {
|
|
2040
2299
|
return;
|
|
2041
2300
|
}
|
|
2042
|
-
if (
|
|
2301
|
+
if (ownedMarker(content, "end", 0).at < ownedMarker(content, "begin", 0).at) {
|
|
2043
2302
|
throw new Error(
|
|
2044
|
-
`
|
|
2303
|
+
`AiNecto markers are out of order (${MARKER_END} appears before ${MARKER_BEGIN}). The file was left untouched \u2014 fix the markers by hand and run again.`
|
|
2045
2304
|
);
|
|
2046
2305
|
}
|
|
2047
2306
|
}
|
|
2048
|
-
function ownedMarkerCount(content,
|
|
2307
|
+
function ownedMarkerCount(content, kind) {
|
|
2049
2308
|
let count = 0;
|
|
2050
|
-
for (let
|
|
2309
|
+
for (let hit2 = ownedMarker(content, kind, 0); hit2 !== void 0; hit2 = ownedMarker(content, kind, hit2.at + 1)) {
|
|
2051
2310
|
count += 1;
|
|
2052
2311
|
}
|
|
2053
2312
|
return count;
|
|
2054
2313
|
}
|
|
2314
|
+
function ownedMarker(content, kind, from) {
|
|
2315
|
+
return hit(ownedMarkerIndex(content, MARKERS[kind], from), MARKERS[kind]);
|
|
2316
|
+
}
|
|
2317
|
+
function anyMarker(content, kind, from) {
|
|
2318
|
+
return hit(content.indexOf(MARKERS[kind], from), MARKERS[kind]);
|
|
2319
|
+
}
|
|
2320
|
+
function hit(at, marker) {
|
|
2321
|
+
return at >= 0 ? { at, length: marker.length } : void 0;
|
|
2322
|
+
}
|
|
2055
2323
|
function isInsideFencedBlock(content, index) {
|
|
2056
2324
|
let open;
|
|
2057
2325
|
let offset = 0;
|
|
@@ -2116,24 +2384,24 @@ function markerEvidence(content) {
|
|
|
2116
2384
|
let begins = 0;
|
|
2117
2385
|
let ends = 0;
|
|
2118
2386
|
while (true) {
|
|
2119
|
-
const begin = rest
|
|
2120
|
-
const close = rest
|
|
2121
|
-
if (begin
|
|
2387
|
+
const begin = anyMarker(rest, "begin", cursor);
|
|
2388
|
+
const close = anyMarker(rest, "end", cursor);
|
|
2389
|
+
if (begin === void 0 && close === void 0) {
|
|
2122
2390
|
break;
|
|
2123
2391
|
}
|
|
2124
|
-
if (begin
|
|
2392
|
+
if (begin === void 0 || close !== void 0 && close.at < begin.at) {
|
|
2125
2393
|
ends += 1;
|
|
2126
|
-
cursor = close +
|
|
2394
|
+
cursor = close.at + close.length;
|
|
2127
2395
|
continue;
|
|
2128
2396
|
}
|
|
2129
|
-
const pairEnd = rest
|
|
2130
|
-
if (pairEnd
|
|
2397
|
+
const pairEnd = anyMarker(rest, "end", begin.at + begin.length);
|
|
2398
|
+
if (pairEnd === void 0) {
|
|
2131
2399
|
begins += 1;
|
|
2132
|
-
cursor = begin +
|
|
2400
|
+
cursor = begin.at + begin.length;
|
|
2133
2401
|
continue;
|
|
2134
2402
|
}
|
|
2135
|
-
evidence.push(blockShape(rest.slice(begin, pairEnd +
|
|
2136
|
-
cursor = pairEnd +
|
|
2403
|
+
evidence.push(blockShape(rest.slice(begin.at, pairEnd.at + pairEnd.length)));
|
|
2404
|
+
cursor = pairEnd.at + pairEnd.length;
|
|
2137
2405
|
}
|
|
2138
2406
|
for (let i = 0; i < begins; i += 1) evidence.push(UNPAIRED_BEGIN);
|
|
2139
2407
|
for (let i = 0; i < ends; i += 1) evidence.push(UNPAIRED_END);
|
|
@@ -2150,15 +2418,15 @@ function existingMarkerBlock(content) {
|
|
|
2150
2418
|
return range ? content.slice(range.start, range.end) : void 0;
|
|
2151
2419
|
}
|
|
2152
2420
|
function markerRange(content) {
|
|
2153
|
-
const start =
|
|
2154
|
-
if (start
|
|
2421
|
+
const start = ownedMarker(content, "begin", 0);
|
|
2422
|
+
if (start === void 0) {
|
|
2155
2423
|
return void 0;
|
|
2156
2424
|
}
|
|
2157
|
-
const endMarker =
|
|
2158
|
-
if (endMarker
|
|
2425
|
+
const endMarker = ownedMarker(content, "end", start.at);
|
|
2426
|
+
if (endMarker === void 0) {
|
|
2159
2427
|
return void 0;
|
|
2160
2428
|
}
|
|
2161
|
-
return { start, end: endMarker +
|
|
2429
|
+
return { start: start.at, end: endMarker.at + endMarker.length };
|
|
2162
2430
|
}
|
|
2163
2431
|
function ownedMarkerIndex(content, marker, from) {
|
|
2164
2432
|
for (let at = content.indexOf(marker, from); at >= 0; at = content.indexOf(marker, at + 1)) {
|
|
@@ -2180,7 +2448,7 @@ function ownedMarkerIndex(content, marker, from) {
|
|
|
2180
2448
|
}
|
|
2181
2449
|
|
|
2182
2450
|
// src/core/harness/initRecord.ts
|
|
2183
|
-
var RECORD_KIND = "
|
|
2451
|
+
var RECORD_KIND = "ainecto/init-record";
|
|
2184
2452
|
var RECORD_VERSION = 2;
|
|
2185
2453
|
var ABSENT = { at: "absent" };
|
|
2186
2454
|
function emptyRecord() {
|
|
@@ -2202,7 +2470,7 @@ function readRecord(content) {
|
|
|
2202
2470
|
if (parsed.kind !== RECORD_KIND) {
|
|
2203
2471
|
return {
|
|
2204
2472
|
state: "corrupt",
|
|
2205
|
-
reason: `${HARNESS_RECORD_PATH} exists but is not an
|
|
2473
|
+
reason: `${HARNESS_RECORD_PATH} exists but is not an AiNecto init record (kind=${JSON.stringify(parsed.kind)})`
|
|
2206
2474
|
};
|
|
2207
2475
|
}
|
|
2208
2476
|
if (parsed.version !== RECORD_VERSION) {
|
|
@@ -2239,46 +2507,6 @@ function readRecord(content) {
|
|
|
2239
2507
|
}
|
|
2240
2508
|
};
|
|
2241
2509
|
}
|
|
2242
|
-
var LEGACY_MANAGED_KEYS = [
|
|
2243
|
-
"created",
|
|
2244
|
-
"originals",
|
|
2245
|
-
"lastWritten",
|
|
2246
|
-
"lastWrittenFragment",
|
|
2247
|
-
"lastWrittenBody",
|
|
2248
|
-
"replacedEntries",
|
|
2249
|
-
"replacedBlocks",
|
|
2250
|
-
"blockAdded",
|
|
2251
|
-
"foreignMarkers"
|
|
2252
|
-
];
|
|
2253
|
-
function legacyManagedRecord(configContent) {
|
|
2254
|
-
if (configContent === void 0) {
|
|
2255
|
-
return false;
|
|
2256
|
-
}
|
|
2257
|
-
let parsed;
|
|
2258
|
-
try {
|
|
2259
|
-
parsed = JSON.parse(configContent);
|
|
2260
|
-
} catch {
|
|
2261
|
-
return false;
|
|
2262
|
-
}
|
|
2263
|
-
if (!isObject(parsed) || !isObject(parsed.managed)) {
|
|
2264
|
-
return false;
|
|
2265
|
-
}
|
|
2266
|
-
const managed = parsed.managed;
|
|
2267
|
-
const hasLegacyShape = LEGACY_MANAGED_KEYS.some(
|
|
2268
|
-
(key) => Object.prototype.hasOwnProperty.call(managed, key)
|
|
2269
|
-
);
|
|
2270
|
-
if (!hasLegacyShape) {
|
|
2271
|
-
return false;
|
|
2272
|
-
}
|
|
2273
|
-
if (typeof parsed.generatedBy === "string") {
|
|
2274
|
-
return true;
|
|
2275
|
-
}
|
|
2276
|
-
const lastWritten = managed.lastWritten;
|
|
2277
|
-
return isObject(lastWritten) && Object.prototype.hasOwnProperty.call(lastWritten, HARNESS_CONFIG_PATH);
|
|
2278
|
-
}
|
|
2279
|
-
function legacyManagedNotice(configPath) {
|
|
2280
|
-
return `${configPath} holds an init record written by an older version of this CLI (a "managed" field). This version keeps its record in ${HARNESS_RECORD_PATH} and cannot read that one, so your saved originals are still in there and nothing was changed. Undo with the version that wrote it (for example: npx @ai-erd/mcp@0.2 ai-erd init --undo), then run this version again.`;
|
|
2281
|
-
}
|
|
2282
2510
|
function serializeRecord(record) {
|
|
2283
2511
|
return `${JSON.stringify({
|
|
2284
2512
|
kind: RECORD_KIND,
|
|
@@ -2297,7 +2525,7 @@ function readContract(raw) {
|
|
|
2297
2525
|
return { problem: `has an unknown scope (${JSON.stringify(raw.scope)})` };
|
|
2298
2526
|
}
|
|
2299
2527
|
if (typeof raw.createdByUs !== "boolean") {
|
|
2300
|
-
return { problem: "does not say whether
|
|
2528
|
+
return { problem: "does not say whether ainecto created the file" };
|
|
2301
2529
|
}
|
|
2302
2530
|
const original = readOwned(raw.original);
|
|
2303
2531
|
const written = readOwned(raw.written);
|
|
@@ -2305,7 +2533,7 @@ function readContract(raw) {
|
|
|
2305
2533
|
return { problem: "has no readable record of what was there before" };
|
|
2306
2534
|
}
|
|
2307
2535
|
if (written === void 0) {
|
|
2308
|
-
return { problem: "has no readable record of what
|
|
2536
|
+
return { problem: "has no readable record of what ainecto wrote" };
|
|
2309
2537
|
}
|
|
2310
2538
|
if (raw.fileFingerprint !== void 0 && typeof raw.fileFingerprint !== "string") {
|
|
2311
2539
|
return { problem: "has a malformed fileFingerprint" };
|
|
@@ -2390,7 +2618,7 @@ function fieldsContractProblem(contract, ownedFields) {
|
|
|
2390
2618
|
if (!Object.prototype.hasOwnProperty.call(body, key)) {
|
|
2391
2619
|
return `says it wrote ${key}, but that field is not in the file body it recorded`;
|
|
2392
2620
|
}
|
|
2393
|
-
if (
|
|
2621
|
+
if (stableStringify2(body[key]) !== stableStringify2(writtenFields[key])) {
|
|
2394
2622
|
return `records a different value for ${key} than the file body it wrote`;
|
|
2395
2623
|
}
|
|
2396
2624
|
}
|
|
@@ -2418,13 +2646,13 @@ function readOwned(raw) {
|
|
|
2418
2646
|
}
|
|
2419
2647
|
return void 0;
|
|
2420
2648
|
}
|
|
2421
|
-
function
|
|
2649
|
+
function stableStringify2(value) {
|
|
2422
2650
|
if (Array.isArray(value)) {
|
|
2423
|
-
return `[${value.map(
|
|
2651
|
+
return `[${value.map(stableStringify2).join(",")}]`;
|
|
2424
2652
|
}
|
|
2425
2653
|
if (value && typeof value === "object") {
|
|
2426
2654
|
const entries = Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
2427
|
-
return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${
|
|
2655
|
+
return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${stableStringify2(item)}`).join(",")}}`;
|
|
2428
2656
|
}
|
|
2429
2657
|
return JSON.stringify(value) ?? "null";
|
|
2430
2658
|
}
|
|
@@ -2436,7 +2664,176 @@ function isStringArray(value) {
|
|
|
2436
2664
|
}
|
|
2437
2665
|
|
|
2438
2666
|
// src/core/harness/initPlan.ts
|
|
2439
|
-
import { createHash as
|
|
2667
|
+
import { createHash as createHash4 } from "crypto";
|
|
2668
|
+
|
|
2669
|
+
// src/core/harness/repositoryRole.ts
|
|
2670
|
+
import { lstat, readFile as readFile4, realpath } from "fs/promises";
|
|
2671
|
+
import { homedir as homedir2 } from "os";
|
|
2672
|
+
import { dirname as dirname2, join as join2, resolve, sep } from "path";
|
|
2673
|
+
function readConfigRole(path, content) {
|
|
2674
|
+
if (content === void 0 || !content.trim()) {
|
|
2675
|
+
return { kind: "absent" };
|
|
2676
|
+
}
|
|
2677
|
+
let servers;
|
|
2678
|
+
try {
|
|
2679
|
+
const parsed = JSON.parse(content);
|
|
2680
|
+
const raw = parsed?.mcpServers;
|
|
2681
|
+
servers = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
|
|
2682
|
+
} catch {
|
|
2683
|
+
return content.includes(SERVER_NAME) ? { kind: "problem", reason: `${path} is not valid JSON` } : { kind: "absent" };
|
|
2684
|
+
}
|
|
2685
|
+
const entry = servers[SERVER_NAME];
|
|
2686
|
+
if (!isOurBridge(entry)) {
|
|
2687
|
+
return { kind: "absent" };
|
|
2688
|
+
}
|
|
2689
|
+
const role = readRoleFromEntry(entry);
|
|
2690
|
+
if (role === void 0) {
|
|
2691
|
+
return { kind: "problem", reason: `${path} has the "${SERVER_NAME}" bridge without a valid --role` };
|
|
2692
|
+
}
|
|
2693
|
+
return { kind: "role", role };
|
|
2694
|
+
}
|
|
2695
|
+
function rolesInFiles(files) {
|
|
2696
|
+
const roles = [];
|
|
2697
|
+
const problems = [];
|
|
2698
|
+
for (const target of AGENT_TARGETS) {
|
|
2699
|
+
const read = readConfigRole(target.path, files.get(target.path));
|
|
2700
|
+
if (read.kind === "problem") {
|
|
2701
|
+
problems.push(read.reason);
|
|
2702
|
+
} else if (read.kind === "role" && !roles.includes(read.role)) {
|
|
2703
|
+
roles.push(read.role);
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
return { roles, problems, found: roles.length > 0 || problems.length > 0 };
|
|
2707
|
+
}
|
|
2708
|
+
async function findRepositoryRole(cwd, home = homedir2()) {
|
|
2709
|
+
const stopAt = await realOrResolved(home);
|
|
2710
|
+
for (let dir = await realOrResolved(cwd); ; dir = dirname2(dir)) {
|
|
2711
|
+
if (samePath(dir, stopAt)) {
|
|
2712
|
+
return { kind: "none" };
|
|
2713
|
+
}
|
|
2714
|
+
const files = /* @__PURE__ */ new Map();
|
|
2715
|
+
let readProblem;
|
|
2716
|
+
for (const target of AGENT_TARGETS) {
|
|
2717
|
+
try {
|
|
2718
|
+
files.set(target.path, await readConfigInside(dir, target.path));
|
|
2719
|
+
} catch (error) {
|
|
2720
|
+
readProblem = error instanceof Error ? error.message : String(error);
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
if (readProblem !== void 0) {
|
|
2724
|
+
return { kind: "conflict", dir, reason: readProblem };
|
|
2725
|
+
}
|
|
2726
|
+
const result = rolesInFiles(files);
|
|
2727
|
+
if (result.found) {
|
|
2728
|
+
if (result.problems.length > 0) {
|
|
2729
|
+
return { kind: "conflict", dir, reason: result.problems.join("; ") };
|
|
2730
|
+
}
|
|
2731
|
+
if (result.roles.length > 1) {
|
|
2732
|
+
return {
|
|
2733
|
+
kind: "conflict",
|
|
2734
|
+
dir,
|
|
2735
|
+
reason: `agent configs disagree about the session role (${result.roles.join(", ")})`
|
|
2736
|
+
};
|
|
2737
|
+
}
|
|
2738
|
+
return { kind: "role", role: result.roles[0], dir };
|
|
2739
|
+
}
|
|
2740
|
+
if (dirname2(dir) === dir) {
|
|
2741
|
+
return { kind: "none" };
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
async function askTheUserToFixRole(dir, reason) {
|
|
2746
|
+
const gitRoot = await findGitRoot(dir);
|
|
2747
|
+
const roles = HARNESS_ROLES.join(", ");
|
|
2748
|
+
if (gitRoot !== void 0 && !samePath(await realOrResolved(gitRoot), await realOrResolved(dir))) {
|
|
2749
|
+
return `Cannot tell this folder's AI session role: ${reason} (in ${dir}, inside the git repository at ${gitRoot}). Ask the user how that AiNecto setting should look \u2014 \`ainecto init\` only sets up a repository root (${gitRoot}).`;
|
|
2750
|
+
}
|
|
2751
|
+
return `Cannot tell this repository's AI session role: ${reason} (in ${dir}). Ask the user which role this repository's AI sessions should have (${roles}), then run \`ainecto init --role <role>\` in ${dir}.`;
|
|
2752
|
+
}
|
|
2753
|
+
async function realOrResolved(path) {
|
|
2754
|
+
try {
|
|
2755
|
+
return await realpath(path);
|
|
2756
|
+
} catch {
|
|
2757
|
+
return resolve(path);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
function samePath(left, right) {
|
|
2761
|
+
return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
2762
|
+
}
|
|
2763
|
+
async function readConfigInside(dir, path) {
|
|
2764
|
+
let real;
|
|
2765
|
+
try {
|
|
2766
|
+
real = await realpath(join2(dir, path));
|
|
2767
|
+
} catch (error) {
|
|
2768
|
+
if (isNodeError2(error, "ENOENT") || isNodeError2(error, "ENOTDIR")) {
|
|
2769
|
+
return void 0;
|
|
2770
|
+
}
|
|
2771
|
+
throw error;
|
|
2772
|
+
}
|
|
2773
|
+
const realDir = await realOrResolved(dir);
|
|
2774
|
+
if (!samePath(real, realDir) && !real.startsWith(realDir + sep) && !(process.platform === "win32" && real.toLowerCase().startsWith((realDir + sep).toLowerCase()))) {
|
|
2775
|
+
throw new Error(`${path} points outside ${dir} (${real})`);
|
|
2776
|
+
}
|
|
2777
|
+
return readFile4(real, "utf8");
|
|
2778
|
+
}
|
|
2779
|
+
async function findGitRoot(cwd) {
|
|
2780
|
+
for (let at = resolve(cwd); ; at = dirname2(at)) {
|
|
2781
|
+
try {
|
|
2782
|
+
await lstat(join2(at, ".git"));
|
|
2783
|
+
return at;
|
|
2784
|
+
} catch {
|
|
2785
|
+
}
|
|
2786
|
+
if (dirname2(at) === at) {
|
|
2787
|
+
return void 0;
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
async function readManagedFile(root, path) {
|
|
2792
|
+
await assertInsideRepository(root, path);
|
|
2793
|
+
return readIfExists(join2(root, path));
|
|
2794
|
+
}
|
|
2795
|
+
async function readIfExists(absolutePath) {
|
|
2796
|
+
try {
|
|
2797
|
+
return await readFile4(absolutePath, "utf8");
|
|
2798
|
+
} catch (error) {
|
|
2799
|
+
if (isNodeError2(error, "ENOENT")) {
|
|
2800
|
+
return void 0;
|
|
2801
|
+
}
|
|
2802
|
+
throw error;
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
async function assertInsideRepository(root, relativePath) {
|
|
2806
|
+
const absolutePath = join2(root, relativePath);
|
|
2807
|
+
const realRoot = await realpath(root);
|
|
2808
|
+
let realParent;
|
|
2809
|
+
try {
|
|
2810
|
+
realParent = await realpath(dirname2(absolutePath));
|
|
2811
|
+
} catch (error) {
|
|
2812
|
+
if (isNodeError2(error, "ENOENT")) {
|
|
2813
|
+
return;
|
|
2814
|
+
}
|
|
2815
|
+
throw error;
|
|
2816
|
+
}
|
|
2817
|
+
if (realParent !== realRoot && !realParent.startsWith(realRoot + sep)) {
|
|
2818
|
+
throw new Error(
|
|
2819
|
+
`${relativePath} resolves outside the repository (${realParent}); refusing to touch it.`
|
|
2820
|
+
);
|
|
2821
|
+
}
|
|
2822
|
+
try {
|
|
2823
|
+
if ((await lstat(absolutePath)).isSymbolicLink()) {
|
|
2824
|
+
throw new Error(`${relativePath} is a symbolic link; refusing to write through it.`);
|
|
2825
|
+
}
|
|
2826
|
+
} catch (error) {
|
|
2827
|
+
if (!isNodeError2(error, "ENOENT")) {
|
|
2828
|
+
throw error;
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
function isNodeError2(error, code) {
|
|
2833
|
+
return typeof error === "object" && error !== null && error.code === code;
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
// src/core/harness/initPlan.ts
|
|
2440
2837
|
var NOTE_TARGETS = [
|
|
2441
2838
|
{ path: "AGENTS.md", createIfMissing: true },
|
|
2442
2839
|
{ path: "CLAUDE.md", createIfMissing: false }
|
|
@@ -2464,10 +2861,10 @@ function sameOwned(left, right) {
|
|
|
2464
2861
|
if (left.at === "text" && right.at === "text") {
|
|
2465
2862
|
return left.text === right.text;
|
|
2466
2863
|
}
|
|
2467
|
-
return left.at === "json" && right.at === "json" &&
|
|
2864
|
+
return left.at === "json" && right.at === "json" && stableStringify2(left.value) === stableStringify2(right.value);
|
|
2468
2865
|
}
|
|
2469
2866
|
function contentFingerprint(content) {
|
|
2470
|
-
return
|
|
2867
|
+
return createHash4("sha256").update(content).digest("hex");
|
|
2471
2868
|
}
|
|
2472
2869
|
function serialize(body) {
|
|
2473
2870
|
return `${JSON.stringify(body, null, 2)}
|
|
@@ -2478,7 +2875,7 @@ function fingerprintFor(path, content) {
|
|
|
2478
2875
|
return contentFingerprint(content);
|
|
2479
2876
|
}
|
|
2480
2877
|
try {
|
|
2481
|
-
return contentFingerprint(
|
|
2878
|
+
return contentFingerprint(stableStringify2(JSON.parse(content)));
|
|
2482
2879
|
} catch {
|
|
2483
2880
|
return contentFingerprint(content);
|
|
2484
2881
|
}
|
|
@@ -2513,9 +2910,6 @@ function initRefusal(files, previous) {
|
|
|
2513
2910
|
if (state.state === "corrupt" || state.state === "legacy") {
|
|
2514
2911
|
return `${state.reason}. Nothing was changed \u2014 move that file aside (or restore it) and run again.`;
|
|
2515
2912
|
}
|
|
2516
|
-
if (state.state === "absent" && legacyManagedRecord(files.get(HARNESS_CONFIG_PATH))) {
|
|
2517
|
-
return legacyManagedNotice(HARNESS_CONFIG_PATH);
|
|
2518
|
-
}
|
|
2519
2913
|
const carried = state.state === "valid" ? state.record : emptyRecord();
|
|
2520
2914
|
for (const [path, contract] of Object.entries(carried.files)) {
|
|
2521
2915
|
if (contract.scope !== "fields") {
|
|
@@ -2558,7 +2952,7 @@ function planInit(input) {
|
|
|
2558
2952
|
return prior;
|
|
2559
2953
|
}
|
|
2560
2954
|
delete record.files[path];
|
|
2561
|
-
notes.push(`${path}: it changed outside
|
|
2955
|
+
notes.push(`${path}: it changed outside ainecto, so the older backup was dropped and this is the new baseline.`);
|
|
2562
2956
|
return void 0;
|
|
2563
2957
|
};
|
|
2564
2958
|
for (const target of AGENT_TARGETS) {
|
|
@@ -2569,7 +2963,7 @@ function planInit(input) {
|
|
|
2569
2963
|
if (existing === merged.content) {
|
|
2570
2964
|
continue;
|
|
2571
2965
|
}
|
|
2572
|
-
const before = previousRole(existing);
|
|
2966
|
+
const before = previousRole(target.path, existing);
|
|
2573
2967
|
if (merged.replaced && before && before !== input.role) {
|
|
2574
2968
|
notes.push(`${target.path}: role ${before} \u2192 ${input.role}`);
|
|
2575
2969
|
}
|
|
@@ -2625,7 +3019,7 @@ function planInit(input) {
|
|
|
2625
3019
|
}
|
|
2626
3020
|
if (carried?.scope === "block" && unexplainedMarkers(existing, carried) > 0) {
|
|
2627
3021
|
notes.push(
|
|
2628
|
-
`${target.path}: this file has an
|
|
3022
|
+
`${target.path}: this file has an ainecto marker block that is not where ainecto can find it again (inside a code fence, a quote, or edited). Left untouched and the earlier backup kept \u2014 move that block back to the top level, or remove it, then run again.`
|
|
2629
3023
|
);
|
|
2630
3024
|
continue;
|
|
2631
3025
|
}
|
|
@@ -2635,7 +3029,7 @@ function planInit(input) {
|
|
|
2635
3029
|
}
|
|
2636
3030
|
const original = prior?.original ?? ownedText(blockNow);
|
|
2637
3031
|
if (prior === void 0 && original.at !== "absent") {
|
|
2638
|
-
notes.push(`${target.path}: an existing
|
|
3032
|
+
notes.push(`${target.path}: an existing ainecto block was saved and will be restored by --undo`);
|
|
2639
3033
|
}
|
|
2640
3034
|
record.files[target.path] = {
|
|
2641
3035
|
scope: "block",
|
|
@@ -2672,11 +3066,11 @@ function planInit(input) {
|
|
|
2672
3066
|
const currentValue = presentNow ? theirConfig[field] : void 0;
|
|
2673
3067
|
const priorOriginal = priorConfig?.originalFields?.[field];
|
|
2674
3068
|
const priorWritten = priorConfig?.writtenFields;
|
|
2675
|
-
const untouchedByUser = priorWritten !== void 0 && hasField(priorWritten, field) &&
|
|
3069
|
+
const untouchedByUser = priorWritten !== void 0 && hasField(priorWritten, field) && stableStringify2(currentValue) === stableStringify2(priorWritten[field]);
|
|
2676
3070
|
const next = untouchedByUser && priorOriginal !== void 0 ? priorOriginal : ownedJson(currentValue, presentNow);
|
|
2677
3071
|
originalFields[field] = next;
|
|
2678
3072
|
if (priorOriginal !== void 0 && !sameOwned(next, priorOriginal)) {
|
|
2679
|
-
notes.push(`${HARNESS_CONFIG_PATH}: you changed ${field} \u2014
|
|
3073
|
+
notes.push(`${HARNESS_CONFIG_PATH}: you changed ${field} \u2014 ainecto now treats your value as the one to put back.`);
|
|
2680
3074
|
}
|
|
2681
3075
|
}
|
|
2682
3076
|
const mergedConfig = serialize({ ...theirConfig ?? {}, ...ourFields });
|
|
@@ -2725,7 +3119,7 @@ function planUndo(input) {
|
|
|
2725
3119
|
notes.push(`If you set ${ROLE_ENV_VAR} anywhere, unset it too.`);
|
|
2726
3120
|
};
|
|
2727
3121
|
if (state.state === "absent") {
|
|
2728
|
-
notes.push(
|
|
3122
|
+
notes.push(`No record of a previous init in ${HARNESS_RECORD_PATH} \u2014 no local file was changed.`);
|
|
2729
3123
|
tail();
|
|
2730
3124
|
return { writes, deletes, codexCommand: codexRemoveCommand(), notes };
|
|
2731
3125
|
}
|
|
@@ -2810,7 +3204,7 @@ function undoEntry(path, contract, existing) {
|
|
|
2810
3204
|
if (sameOwned(currentOwned, contract.original)) {
|
|
2811
3205
|
return {
|
|
2812
3206
|
kind: "done",
|
|
2813
|
-
note: contract.original.at === "absent" ? `${path}: the "${SERVER_NAME}" entry
|
|
3207
|
+
note: contract.original.at === "absent" ? `${path}: the "${SERVER_NAME}" entry ainecto added is already gone.` : `${path}: your original "${SERVER_NAME}" entry is already back in place.`
|
|
2814
3208
|
};
|
|
2815
3209
|
}
|
|
2816
3210
|
const goneButWeHoldTheOriginal = current === void 0 && contract.original.at !== "absent";
|
|
@@ -2840,13 +3234,13 @@ function undoBlock(path, contract, existing) {
|
|
|
2840
3234
|
if (unexplainedMarkers(existing, contract) > 0) {
|
|
2841
3235
|
return {
|
|
2842
3236
|
kind: "unresolved",
|
|
2843
|
-
note: `${path}: this file has an
|
|
3237
|
+
note: `${path}: this file has an ainecto marker block that --undo cannot account for (inside a code fence, a quote, or edited). Remove it by hand, then run --undo again.`
|
|
2844
3238
|
};
|
|
2845
3239
|
}
|
|
2846
3240
|
if (sameOwned(currentOwned, contract.original)) {
|
|
2847
3241
|
return {
|
|
2848
3242
|
kind: "done",
|
|
2849
|
-
note: contract.original.at === "absent" ? `${path}: the
|
|
3243
|
+
note: contract.original.at === "absent" ? `${path}: the ainecto block is already gone.` : `${path}: the ainecto block that was there before is already back in place.`
|
|
2850
3244
|
};
|
|
2851
3245
|
}
|
|
2852
3246
|
if (current === void 0 && contract.original.at === "text") {
|
|
@@ -2855,12 +3249,12 @@ function undoBlock(path, contract, existing) {
|
|
|
2855
3249
|
${contract.original.text}
|
|
2856
3250
|
` : `${contract.original.text}
|
|
2857
3251
|
`;
|
|
2858
|
-
return { kind: "write", content: body, note: `${path}: restored the
|
|
3252
|
+
return { kind: "write", content: body, note: `${path}: restored the ainecto block that was there before.` };
|
|
2859
3253
|
}
|
|
2860
3254
|
if (!sameOwned(currentOwned, contract.written)) {
|
|
2861
3255
|
return {
|
|
2862
3256
|
kind: "unresolved",
|
|
2863
|
-
note: `${path}: the
|
|
3257
|
+
note: `${path}: the ainecto block was edited after init \u2014 left in place. Remove the block by hand if you meant to, then run --undo again.`
|
|
2864
3258
|
};
|
|
2865
3259
|
}
|
|
2866
3260
|
let result;
|
|
@@ -2872,7 +3266,7 @@ ${contract.original.text}
|
|
|
2872
3266
|
if (!result.removed) {
|
|
2873
3267
|
return {
|
|
2874
3268
|
kind: "unresolved",
|
|
2875
|
-
note: `${path}: the
|
|
3269
|
+
note: `${path}: the ainecto block could not be removed \u2014 it is still in the file. Remove it by hand, then run --undo again.`
|
|
2876
3270
|
};
|
|
2877
3271
|
}
|
|
2878
3272
|
if (result.emptied && contract.createdByUs) {
|
|
@@ -2881,7 +3275,7 @@ ${contract.original.text}
|
|
|
2881
3275
|
return {
|
|
2882
3276
|
kind: "write",
|
|
2883
3277
|
content: result.content,
|
|
2884
|
-
note: contract.original.at === "text" ? `${path}: restored the
|
|
3278
|
+
note: contract.original.at === "text" ? `${path}: restored the ainecto block that was there before.` : void 0
|
|
2885
3279
|
};
|
|
2886
3280
|
}
|
|
2887
3281
|
function undoWhole(path, contract, existing) {
|
|
@@ -2911,14 +3305,14 @@ function undoFields(path, contract, existing) {
|
|
|
2911
3305
|
if (parsed === void 0) {
|
|
2912
3306
|
return {
|
|
2913
3307
|
kind: "unresolved",
|
|
2914
|
-
note: `${path} is not a JSON object any more, so
|
|
3308
|
+
note: `${path} is not a JSON object any more, so ainecto could not take its fields back out \u2014 the backup is kept. Fix the file and run --undo again.`
|
|
2915
3309
|
};
|
|
2916
3310
|
}
|
|
2917
3311
|
const contractProblem = fieldsContractProblem(contract, OUR_CONFIG_FIELDS);
|
|
2918
3312
|
if (contractProblem !== void 0) {
|
|
2919
3313
|
return {
|
|
2920
3314
|
kind: "unresolved",
|
|
2921
|
-
note: `${path}:
|
|
3315
|
+
note: `${path}: ainecto's record of the fields it wrote ${contractProblem}, so its fields were left in place \u2014 the backup is kept. Restore that record, or remove the fields by hand.`
|
|
2922
3316
|
};
|
|
2923
3317
|
}
|
|
2924
3318
|
const written = contract.writtenFields;
|
|
@@ -2933,10 +3327,10 @@ function undoFields(path, contract, existing) {
|
|
|
2933
3327
|
if (!hasField(written, field)) {
|
|
2934
3328
|
return {
|
|
2935
3329
|
kind: "unresolved",
|
|
2936
|
-
note: `${path}:
|
|
3330
|
+
note: `${path}: ainecto has no record of what it wrote for ${field}, so its fields were left in place \u2014 the backup is kept. Restore that record, or remove the field by hand.`
|
|
2937
3331
|
};
|
|
2938
3332
|
}
|
|
2939
|
-
const stillOurs =
|
|
3333
|
+
const stillOurs = stableStringify2(value) === stableStringify2(written[field]);
|
|
2940
3334
|
if (!stillOurs) {
|
|
2941
3335
|
next[field] = value;
|
|
2942
3336
|
kept.push(field);
|
|
@@ -2946,7 +3340,7 @@ function undoFields(path, contract, existing) {
|
|
|
2946
3340
|
if (original === void 0 || original.at !== "json" && original.at !== "absent") {
|
|
2947
3341
|
return {
|
|
2948
3342
|
kind: "unresolved",
|
|
2949
|
-
note: `${path}:
|
|
3343
|
+
note: `${path}: ainecto cannot tell what was in ${field} before it wrote there, so the file was left as it is \u2014 the backup is kept. Remove the field by hand if you meant to.`
|
|
2950
3344
|
};
|
|
2951
3345
|
}
|
|
2952
3346
|
if (original.at === "json") {
|
|
@@ -2961,7 +3355,7 @@ function undoFields(path, contract, existing) {
|
|
|
2961
3355
|
return body === existing ? { kind: "done", note } : {
|
|
2962
3356
|
kind: "write",
|
|
2963
3357
|
content: body,
|
|
2964
|
-
note: note ?? `${path}: removed what
|
|
3358
|
+
note: note ?? `${path}: removed what ainecto wrote and kept the rest.`
|
|
2965
3359
|
};
|
|
2966
3360
|
}
|
|
2967
3361
|
function withCreatedDirectories(current, directories) {
|
|
@@ -2981,27 +3375,9 @@ function parentsOf(paths) {
|
|
|
2981
3375
|
}
|
|
2982
3376
|
return [...directories].sort((left, right) => right.length - left.length);
|
|
2983
3377
|
}
|
|
2984
|
-
function
|
|
2985
|
-
const
|
|
2986
|
-
|
|
2987
|
-
const existing = files.get(target.path);
|
|
2988
|
-
if (existing === void 0) {
|
|
2989
|
-
continue;
|
|
2990
|
-
}
|
|
2991
|
-
found.set(target.path, previousRole(existing));
|
|
2992
|
-
}
|
|
2993
|
-
return found;
|
|
2994
|
-
}
|
|
2995
|
-
function previousRole(existing) {
|
|
2996
|
-
if (existing === void 0) {
|
|
2997
|
-
return void 0;
|
|
2998
|
-
}
|
|
2999
|
-
try {
|
|
3000
|
-
const parsed = JSON.parse(existing);
|
|
3001
|
-
return readRoleFromEntry(parsed.mcpServers?.[SERVER_NAME]);
|
|
3002
|
-
} catch {
|
|
3003
|
-
return void 0;
|
|
3004
|
-
}
|
|
3378
|
+
function previousRole(path, existing) {
|
|
3379
|
+
const read = readConfigRole(path, existing);
|
|
3380
|
+
return read.kind === "role" ? read.role : void 0;
|
|
3005
3381
|
}
|
|
3006
3382
|
|
|
3007
3383
|
// src/core/harness/documentFetch.ts
|
|
@@ -3057,10 +3433,97 @@ function usableBodyProblem(key, entry) {
|
|
|
3057
3433
|
return documentContractProblem(key, entry.body);
|
|
3058
3434
|
}
|
|
3059
3435
|
|
|
3436
|
+
// src/adapters/cli/prompter.ts
|
|
3437
|
+
import { createInterface as createInterface3 } from "readline";
|
|
3438
|
+
var PromptCancelled = class extends Error {
|
|
3439
|
+
constructor() {
|
|
3440
|
+
super("Cancelled \u2014 no repository files were written. A sign-in you already completed stays saved.");
|
|
3441
|
+
this.name = "PromptCancelled";
|
|
3442
|
+
}
|
|
3443
|
+
};
|
|
3444
|
+
var AGENT_ENV_MARKERS = ["CLAUDECODE", "CI", "GEMINI_CLI", "CODEX_SANDBOX", "CURSOR_AGENT"];
|
|
3445
|
+
function agentEnvironment(env = process.env) {
|
|
3446
|
+
return AGENT_ENV_MARKERS.find((name) => {
|
|
3447
|
+
const value = env[name]?.trim().toLowerCase();
|
|
3448
|
+
return value !== void 0 && value !== "" && value !== "0" && value !== "false";
|
|
3449
|
+
});
|
|
3450
|
+
}
|
|
3451
|
+
function isInteractive(io, json, env = process.env) {
|
|
3452
|
+
return io.stdin?.isTTY === true && io.stdout?.isTTY === true && !json && agentEnvironment(env) === void 0;
|
|
3453
|
+
}
|
|
3454
|
+
function createReadlinePrompter(io) {
|
|
3455
|
+
let rl;
|
|
3456
|
+
let lines;
|
|
3457
|
+
const readLine = async (prompt) => {
|
|
3458
|
+
io.stderr.write(prompt);
|
|
3459
|
+
if (!rl) {
|
|
3460
|
+
rl = createInterface3({ input: io.stdin, terminal: false });
|
|
3461
|
+
lines = rl[Symbol.asyncIterator]();
|
|
3462
|
+
}
|
|
3463
|
+
const next = await lines.next();
|
|
3464
|
+
if (next.done) {
|
|
3465
|
+
io.stderr.write("\n");
|
|
3466
|
+
throw new PromptCancelled();
|
|
3467
|
+
}
|
|
3468
|
+
return next.value;
|
|
3469
|
+
};
|
|
3470
|
+
return {
|
|
3471
|
+
choose: (question, choices, defaultIndex) => chooseWith(readLine, (text) => io.stderr.write(text), question, choices, defaultIndex),
|
|
3472
|
+
text: async (question, defaultValue) => {
|
|
3473
|
+
const answer = (await readLine(`${question} [${defaultValue}]: `)).trim();
|
|
3474
|
+
return answer || defaultValue;
|
|
3475
|
+
},
|
|
3476
|
+
close: () => {
|
|
3477
|
+
rl?.close();
|
|
3478
|
+
}
|
|
3479
|
+
};
|
|
3480
|
+
}
|
|
3481
|
+
async function chooseWith(readLine, write, question, choices, defaultIndex) {
|
|
3482
|
+
if (choices.length === 0) {
|
|
3483
|
+
throw new Error(`Nothing to choose for: ${question}`);
|
|
3484
|
+
}
|
|
3485
|
+
write(`${question}
|
|
3486
|
+
`);
|
|
3487
|
+
choices.forEach((choice, index) => write(` [${index + 1}] ${choice.label}
|
|
3488
|
+
`));
|
|
3489
|
+
const hint = defaultIndex === void 0 ? "" : ` [${defaultIndex + 1}]`;
|
|
3490
|
+
for (; ; ) {
|
|
3491
|
+
const answer = (await readLine(`Enter a number (1-${choices.length})${hint}: `)).trim();
|
|
3492
|
+
if (answer === "" && defaultIndex !== void 0) {
|
|
3493
|
+
return choices[defaultIndex].value;
|
|
3494
|
+
}
|
|
3495
|
+
const picked = /^\d+$/.test(answer) ? Number(answer) : NaN;
|
|
3496
|
+
if (picked >= 1 && picked <= choices.length) {
|
|
3497
|
+
return choices[picked - 1].value;
|
|
3498
|
+
}
|
|
3499
|
+
write(answer === "" ? "Please choose one \u2014 there is no default here.\n" : `"${answer}" is not one of the numbers above.
|
|
3500
|
+
`);
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3060
3504
|
// src/adapters/cli/initCommand.ts
|
|
3505
|
+
function singleRole(roles) {
|
|
3506
|
+
return roles.length === 1 ? roles[0] : void 0;
|
|
3507
|
+
}
|
|
3061
3508
|
async function executeInitCommand(options) {
|
|
3509
|
+
try {
|
|
3510
|
+
return await runInit(options);
|
|
3511
|
+
} catch (error) {
|
|
3512
|
+
if (error instanceof PromptCancelled) {
|
|
3513
|
+
options.io.stderr.write(`${error.message}
|
|
3514
|
+
`);
|
|
3515
|
+
return 130;
|
|
3516
|
+
}
|
|
3517
|
+
throw error;
|
|
3518
|
+
} finally {
|
|
3519
|
+
options.prompter?.close();
|
|
3520
|
+
}
|
|
3521
|
+
}
|
|
3522
|
+
async function runInit(initialOptions) {
|
|
3523
|
+
let options = initialOptions;
|
|
3062
3524
|
const args = parseInitArgs(options.argv);
|
|
3063
|
-
const root =
|
|
3525
|
+
const root = resolve2(options.cwd);
|
|
3526
|
+
const rootNotes = await repositoryRootNotes(root);
|
|
3064
3527
|
const files = await readSnapshot(root);
|
|
3065
3528
|
if (args.undo) {
|
|
3066
3529
|
const plan2 = planUndo({ files });
|
|
@@ -3070,23 +3533,34 @@ async function executeInitCommand(options) {
|
|
|
3070
3533
|
action: args.dryRun ? "undo (dry-run)" : "undo",
|
|
3071
3534
|
updated: plan2.writes.map((write) => write.path),
|
|
3072
3535
|
removed: plan2.deletes,
|
|
3073
|
-
notes: plan2.notes
|
|
3536
|
+
notes: [...rootNotes, ...plan2.notes]
|
|
3074
3537
|
},
|
|
3075
3538
|
{ json: options.json }
|
|
3076
3539
|
));
|
|
3077
3540
|
return 0;
|
|
3078
3541
|
}
|
|
3079
|
-
const role = options.role ?? currentRole(files);
|
|
3080
|
-
if (!role) {
|
|
3081
|
-
throw new Error(
|
|
3082
|
-
`--role is required on first run. Pick one of: ${HARNESS_ROLES.join(", ")}.`
|
|
3083
|
-
);
|
|
3084
|
-
}
|
|
3085
3542
|
const refusal = initRefusal(files);
|
|
3086
3543
|
if (refusal !== void 0) {
|
|
3087
3544
|
throw new Error(refusal);
|
|
3088
3545
|
}
|
|
3089
|
-
const
|
|
3546
|
+
const prompter = options.prompter;
|
|
3547
|
+
const role = options.role ?? (prompter ? await askRole(prompter, singleRole(rolesInFiles(files).roles)) : currentRole(files));
|
|
3548
|
+
const previousRole2 = (() => {
|
|
3549
|
+
try {
|
|
3550
|
+
return currentRole(files);
|
|
3551
|
+
} catch {
|
|
3552
|
+
return void 0;
|
|
3553
|
+
}
|
|
3554
|
+
})();
|
|
3555
|
+
if (!role) {
|
|
3556
|
+
throw new Error(
|
|
3557
|
+
`No role given. Ask the user which role this repository's AI sessions should have (${HARNESS_ROLES.join(", ")}), then re-run with --role <role>.`
|
|
3558
|
+
);
|
|
3559
|
+
}
|
|
3560
|
+
const connected = connectAs(options, role);
|
|
3561
|
+
options = connected;
|
|
3562
|
+
await ensureSignedIn(connected, role, args.dryRun);
|
|
3563
|
+
const project = await resolveProject(connected, args, files, root, role);
|
|
3090
3564
|
if ("choices" in project) {
|
|
3091
3565
|
options.io.stdout.write(renderSuccess(
|
|
3092
3566
|
{
|
|
@@ -3100,7 +3574,8 @@ async function executeInitCommand(options) {
|
|
|
3100
3574
|
const documents = args.dryRun ? { versions: {}, fallbacks: {}, unavailableReason: "skipped in --dry-run" } : await fetchHarnessDocuments({
|
|
3101
3575
|
endpoint: options.endpoint,
|
|
3102
3576
|
accessToken: await options.accessToken?.(),
|
|
3103
|
-
|
|
3577
|
+
// 서버가 문서의 클라이언트별 변형 키를 이 이름으로 찾는다.
|
|
3578
|
+
client: "ainecto-cli",
|
|
3104
3579
|
fetchImpl: options.fetchImpl
|
|
3105
3580
|
});
|
|
3106
3581
|
const plan = planInit({
|
|
@@ -3117,6 +3592,10 @@ async function executeInitCommand(options) {
|
|
|
3117
3592
|
throw new Error(plan.refusal);
|
|
3118
3593
|
}
|
|
3119
3594
|
await applyPlan(root, plan, args.dryRun, files);
|
|
3595
|
+
if (!args.dryRun && previousRole2 !== void 0 && previousRole2 !== role) {
|
|
3596
|
+
options.io.stderr.write(`Role changes from ${previousRole2} to ${role}: \`ainecto\` commands now, MCP tools from a new session.
|
|
3597
|
+
`);
|
|
3598
|
+
}
|
|
3120
3599
|
options.io.stdout.write(renderSuccess(
|
|
3121
3600
|
{
|
|
3122
3601
|
action: args.dryRun ? "init (dry-run)" : "init",
|
|
@@ -3130,36 +3609,34 @@ async function executeInitCommand(options) {
|
|
|
3130
3609
|
// ⚠조용히 기본값으로 떨어지지 않는다 — 서버에서 규칙을 고쳐도 안 바뀌는 이유를
|
|
3131
3610
|
// 사용자가 영영 못 찾게 된다. ★요청 자체의 실패와 «문서별» 되돌림을 둘 다 적는다
|
|
3132
3611
|
// (2026-09-23 독립 재리뷰 I5: HTTP 200 으로 온 빈 문서가 조용히 패키지 판이 됐다).
|
|
3133
|
-
notes: [...plan.notes, ...fallbackNotes(documents)],
|
|
3612
|
+
notes: [...rootNotes, ...plan.notes, ...fallbackNotes(documents)],
|
|
3613
|
+
// ★「Enforce this on the server too: ainecto auth login …」 줄은 뺐다. init 이 이미 그 역할로
|
|
3614
|
+
// 로그인했으므로 참이 아니고, 남아 있으면 에이전트가 따라 쳐서 사람이 한 번 더 승인하게 된다
|
|
3615
|
+
// (2026-09-29 독립 리뷰 P1-1).
|
|
3616
|
+
// ★재시작 없이 이어간다(2026-09-30 사용자 결정). 셸의 `ainecto` 는 부를 때마다 저장소 역할을 읽어
|
|
3617
|
+
// 역할 헤더로 싣는다 — 서버가 요청마다 강제하므로 MCP 연결과 무관하게 이 세션에서 바로 맞다.
|
|
3618
|
+
// 재시작은 «그 역할의 MCP 도구»를 더할 뿐인 선택이다.
|
|
3134
3619
|
next: [
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
"
|
|
3620
|
+
`Keep working in this session \u2014 no restart is needed. \`ainecto\` commands run from this repository use the ${role} role at once.`,
|
|
3621
|
+
`Read ${HARNESS_DOC_PATH} first, then \`ainecto tools list\` shows the role's tools and \`ainecto tools call <tool> '<json>'\` calls one.`,
|
|
3622
|
+
"Do not use MCP tools this session started with for this work \u2014 they keep the role (or no role) the session started with.",
|
|
3623
|
+
"Optional, to add the role's MCP tools: start a new session.",
|
|
3624
|
+
`Claude Code: exit and run \`claude -c\` in this folder, then approve the "${SERVER_NAME}" server from .mcp.json when asked.`,
|
|
3625
|
+
`Codex: save the profile above as $CODEX_HOME/${role}.config.toml and start with \`codex -p ${role}\`.`,
|
|
3626
|
+
role === "design" ? "To check the role, change a table through `ainecto` \u2014 Design may, and the change lands in AiNecto." : `To check the role, try changing a table through \`ainecto\` \u2014 ${role} is told to stop.`,
|
|
3627
|
+
`If the new Claude Code session still has no role, run \`claude mcp get ${SERVER_NAME}\`: a local-scope "${SERVER_NAME}" entry hides .mcp.json (remove it with \`claude mcp remove ${SERVER_NAME} -s local\`).`,
|
|
3628
|
+
"Undo everything with: ainecto init --undo"
|
|
3139
3629
|
]
|
|
3140
3630
|
},
|
|
3141
3631
|
{ json: options.json }
|
|
3142
3632
|
));
|
|
3143
3633
|
return 0;
|
|
3144
3634
|
}
|
|
3145
|
-
async function readRepositoryRole(cwd) {
|
|
3146
|
-
const root = resolve(cwd);
|
|
3147
|
-
const files = /* @__PURE__ */ new Map();
|
|
3148
|
-
for (const target of AGENT_TARGETS) {
|
|
3149
|
-
files.set(target.path, await readManagedFile(root, target.path));
|
|
3150
|
-
}
|
|
3151
|
-
try {
|
|
3152
|
-
return currentRole(files);
|
|
3153
|
-
} catch {
|
|
3154
|
-
return void 0;
|
|
3155
|
-
}
|
|
3156
|
-
}
|
|
3157
3635
|
function currentRole(files) {
|
|
3158
|
-
const
|
|
3159
|
-
const unique = [...new Set(found)];
|
|
3636
|
+
const unique = rolesInFiles(files).roles;
|
|
3160
3637
|
if (unique.length > 1) {
|
|
3161
3638
|
throw new Error(
|
|
3162
|
-
`Agent configs disagree about the session role (${unique.join(", ")}).
|
|
3639
|
+
`Agent configs in this repository disagree about the session role (${unique.join(", ")}). Ask the user which role this repository's AI sessions should have (${HARNESS_ROLES.join(", ")}), then re-run with --role <role> to line them up.`
|
|
3163
3640
|
);
|
|
3164
3641
|
}
|
|
3165
3642
|
return unique[0];
|
|
@@ -3174,13 +3651,108 @@ function fallbackNotes(documents) {
|
|
|
3174
3651
|
}
|
|
3175
3652
|
return notes;
|
|
3176
3653
|
}
|
|
3177
|
-
async function
|
|
3654
|
+
async function ensureSignedIn(options, role, dryRun) {
|
|
3655
|
+
if (options.login === void 0) {
|
|
3656
|
+
return;
|
|
3657
|
+
}
|
|
3658
|
+
if (await options.accessToken?.()) {
|
|
3659
|
+
return;
|
|
3660
|
+
}
|
|
3661
|
+
if (dryRun) {
|
|
3662
|
+
throw new Error(
|
|
3663
|
+
`No usable AiNecto sign-in on this machine, and --dry-run neither refreshes it nor opens a browser.
|
|
3664
|
+
|
|
3665
|
+
${loginCommand(options)}
|
|
3666
|
+
|
|
3667
|
+
Then run init again.`
|
|
3668
|
+
);
|
|
3669
|
+
}
|
|
3670
|
+
await options.login();
|
|
3671
|
+
options.io.stderr.write("Signed in.\n");
|
|
3672
|
+
}
|
|
3673
|
+
async function repositoryRootNotes(cwd) {
|
|
3674
|
+
const gitRoot = await findGitRoot(cwd);
|
|
3675
|
+
if (gitRoot === void 0) {
|
|
3676
|
+
return [`${cwd} is not inside a git repository, so this folder was used as the repository root.`];
|
|
3677
|
+
}
|
|
3678
|
+
if (gitRoot !== cwd) {
|
|
3679
|
+
throw new Error(
|
|
3680
|
+
`This folder is inside the git repository at ${gitRoot}, not at its root. Agents read their MCP config from the repository root, so run init there:
|
|
3681
|
+
|
|
3682
|
+
cd ${gitRoot}
|
|
3683
|
+
|
|
3684
|
+
Nothing was changed.`
|
|
3685
|
+
);
|
|
3686
|
+
}
|
|
3687
|
+
return [];
|
|
3688
|
+
}
|
|
3689
|
+
function connectAs(options, role) {
|
|
3690
|
+
if (options.connect) {
|
|
3691
|
+
const connection = options.connect(role);
|
|
3692
|
+
return { ...options, ...connection };
|
|
3693
|
+
}
|
|
3694
|
+
if (!options.client) {
|
|
3695
|
+
throw new Error("init needs either a client or a way to connect once the role is known.");
|
|
3696
|
+
}
|
|
3697
|
+
return { ...options, client: options.client };
|
|
3698
|
+
}
|
|
3699
|
+
var ROLE_CHOICES = [
|
|
3700
|
+
{ label: "Design requirements, ERD, boundaries, tasks", value: "design" },
|
|
3701
|
+
{ label: "Development product code, following the approved design", value: "development" },
|
|
3702
|
+
{ label: "Test test scenarios and test code", value: "test" },
|
|
3703
|
+
{ label: "Validation judges the result; changes nothing", value: "validation" }
|
|
3704
|
+
];
|
|
3705
|
+
async function askRole(prompter, current) {
|
|
3706
|
+
const index = current === void 0 ? void 0 : ROLE_CHOICES.findIndex((choice) => choice.value === current);
|
|
3707
|
+
return prompter.choose(
|
|
3708
|
+
current === void 0 ? "Which role should AI sessions in this repository have?" : `Which role should AI sessions in this repository have? (now: ${current})`,
|
|
3709
|
+
ROLE_CHOICES,
|
|
3710
|
+
index
|
|
3711
|
+
);
|
|
3712
|
+
}
|
|
3713
|
+
async function askProject(options, args, projects, bound, root, role, prompter) {
|
|
3714
|
+
const canCreate = role === "design" && !args.dryRun;
|
|
3715
|
+
const askNameAndCreate = async () => {
|
|
3716
|
+
const name = await prompter.text("New project name", args.projectName ?? basenameOf(root));
|
|
3717
|
+
return createProject(options, name);
|
|
3718
|
+
};
|
|
3719
|
+
if (projects.length === 0) {
|
|
3720
|
+
if (role !== "design") {
|
|
3721
|
+
throw new Error(noProjectForRole(options, role));
|
|
3722
|
+
}
|
|
3723
|
+
if (args.dryRun) {
|
|
3724
|
+
throw new Error(dryRunCannotCreate(args.projectName ?? basenameOf(root)));
|
|
3725
|
+
}
|
|
3726
|
+
return askNameAndCreate();
|
|
3727
|
+
}
|
|
3728
|
+
const choices = projects.map((project) => ({
|
|
3729
|
+
label: `${project.name} (${project.uuid})`,
|
|
3730
|
+
value: { project }
|
|
3731
|
+
}));
|
|
3732
|
+
if (canCreate) {
|
|
3733
|
+
choices.push({ label: "Create a new project", value: { create: true } });
|
|
3734
|
+
}
|
|
3735
|
+
const boundIndex = bound === void 0 ? -1 : projects.findIndex((project) => project.uuid === bound);
|
|
3736
|
+
const defaultIndex = boundIndex >= 0 ? boundIndex : projects.length === 1 ? 0 : void 0;
|
|
3737
|
+
const picked = await prompter.choose("Which AiNecto project should this repository use?", choices, defaultIndex);
|
|
3738
|
+
return "create" in picked ? askNameAndCreate() : picked.project;
|
|
3739
|
+
}
|
|
3740
|
+
function noProjectForRole(options, role) {
|
|
3741
|
+
return `No project found in your AiNecto account, and only a Design session can create one. Ask the user: create the project at ${new URL(options.endpoint).origin} or in a Design session, then run init again with --role ${role}.`;
|
|
3742
|
+
}
|
|
3743
|
+
function dryRunCannotCreate(name) {
|
|
3744
|
+
return `--dry-run cannot continue: there is no project to bind, and creating "${name}" would be a real change. Re-run without --dry-run, or pass --project <uuid>.`;
|
|
3745
|
+
}
|
|
3746
|
+
async function resolveProject(options, args, files, root, role) {
|
|
3178
3747
|
const projects = await listProjects(options);
|
|
3179
3748
|
if (args.projectUuid) {
|
|
3180
3749
|
const found = projects.find((project) => project.uuid === args.projectUuid);
|
|
3181
3750
|
return found ?? { uuid: args.projectUuid, name: args.projectName ?? args.projectUuid };
|
|
3182
3751
|
}
|
|
3183
3752
|
const bound = boundProjectUuid(files.get(HARNESS_CONFIG_PATH));
|
|
3753
|
+
if (options.prompter) {
|
|
3754
|
+
return askProject(options, args, projects, bound, root, role, options.prompter);
|
|
3755
|
+
}
|
|
3184
3756
|
if (bound) {
|
|
3185
3757
|
const found = projects.find((project) => project.uuid === bound);
|
|
3186
3758
|
if (found) {
|
|
@@ -3193,16 +3765,17 @@ async function resolveProject(options, args, files, root) {
|
|
|
3193
3765
|
if (projects.length > 1) {
|
|
3194
3766
|
return { choices: projects };
|
|
3195
3767
|
}
|
|
3768
|
+
if (role !== "design") {
|
|
3769
|
+
throw new Error(noProjectForRole(options, role));
|
|
3770
|
+
}
|
|
3196
3771
|
const name = args.projectName ?? basenameOf(root);
|
|
3197
3772
|
if (!args.yes) {
|
|
3198
3773
|
throw new Error(
|
|
3199
|
-
`No project found in your
|
|
3774
|
+
`No project found in your AiNecto account. Re-run with --yes to create "${name}", or --project-name <name> --yes to pick the name.`
|
|
3200
3775
|
);
|
|
3201
3776
|
}
|
|
3202
3777
|
if (args.dryRun) {
|
|
3203
|
-
throw new Error(
|
|
3204
|
-
`--dry-run cannot continue: there is no project to bind, and creating "${name}" would be a real change. Re-run without --dry-run, or pass --project <uuid>.`
|
|
3205
|
-
);
|
|
3778
|
+
throw new Error(dryRunCannotCreate(name));
|
|
3206
3779
|
}
|
|
3207
3780
|
return createProject(options, name);
|
|
3208
3781
|
}
|
|
@@ -3218,17 +3791,17 @@ async function callOrExplainSignIn(options, run) {
|
|
|
3218
3791
|
throw error;
|
|
3219
3792
|
}
|
|
3220
3793
|
throw new Error(
|
|
3221
|
-
`Not signed in
|
|
3794
|
+
`Not signed in, so ${options.endpoint} refused the request (HTTP 401).
|
|
3222
3795
|
|
|
3223
|
-
${loginCommand(options
|
|
3796
|
+
${loginCommand(options)}
|
|
3224
3797
|
|
|
3225
|
-
Then run init again.
|
|
3798
|
+
Then run init again. Sign-in is one per machine; the role comes from this repository.`
|
|
3226
3799
|
);
|
|
3227
3800
|
}
|
|
3228
3801
|
}
|
|
3229
|
-
function loginCommand(options
|
|
3802
|
+
function loginCommand(options) {
|
|
3230
3803
|
const target = isDefaultEndpointFor(options.env, options.endpoint) ? options.env === "prod" ? "" : ` --env ${options.env}` : ` --endpoint ${options.endpoint}`;
|
|
3231
|
-
return `
|
|
3804
|
+
return `ainecto auth login${target}`;
|
|
3232
3805
|
}
|
|
3233
3806
|
function isUnauthorized(error) {
|
|
3234
3807
|
return error instanceof McpRpcError && error.code === "MCP_HTTP_ERROR" && error.details?.status === 401;
|
|
@@ -3240,14 +3813,16 @@ async function createProject(options, name) {
|
|
|
3240
3813
|
if (!first) {
|
|
3241
3814
|
throw new Error("Project creation did not return a project uuid.");
|
|
3242
3815
|
}
|
|
3816
|
+
options.io.stderr.write(`Created project ${first.name} (${first.uuid}).
|
|
3817
|
+
`);
|
|
3243
3818
|
return first;
|
|
3244
3819
|
}
|
|
3245
3820
|
function unwrapToolJson(result) {
|
|
3246
|
-
if (!
|
|
3821
|
+
if (!isRecord9(result) || !Array.isArray(result.content)) {
|
|
3247
3822
|
return result;
|
|
3248
3823
|
}
|
|
3249
|
-
const first = result.content.find((item) =>
|
|
3250
|
-
if (!
|
|
3824
|
+
const first = result.content.find((item) => isRecord9(item) && item.type === "text");
|
|
3825
|
+
if (!isRecord9(first) || typeof first.text !== "string") {
|
|
3251
3826
|
return result;
|
|
3252
3827
|
}
|
|
3253
3828
|
try {
|
|
@@ -3257,13 +3832,13 @@ function unwrapToolJson(result) {
|
|
|
3257
3832
|
}
|
|
3258
3833
|
}
|
|
3259
3834
|
function extractProjects(value) {
|
|
3260
|
-
const candidates = Array.isArray(value) ? value :
|
|
3835
|
+
const candidates = Array.isArray(value) ? value : isRecord9(value) ? ["projects", "items", "created", "data", "hits"].flatMap((key) => {
|
|
3261
3836
|
const nested = value[key];
|
|
3262
3837
|
return Array.isArray(nested) ? nested : [];
|
|
3263
3838
|
}) : [];
|
|
3264
3839
|
const projects = [];
|
|
3265
3840
|
for (const candidate of candidates) {
|
|
3266
|
-
if (
|
|
3841
|
+
if (isRecord9(candidate) && typeof candidate.uuid === "string") {
|
|
3267
3842
|
projects.push({
|
|
3268
3843
|
uuid: candidate.uuid,
|
|
3269
3844
|
name: typeof candidate.name === "string" ? candidate.name : candidate.uuid
|
|
@@ -3290,47 +3865,6 @@ async function readSnapshot(root) {
|
|
|
3290
3865
|
}
|
|
3291
3866
|
return snapshot;
|
|
3292
3867
|
}
|
|
3293
|
-
async function readManagedFile(root, path) {
|
|
3294
|
-
await assertInsideRepository(root, path);
|
|
3295
|
-
return readIfExists(join2(root, path));
|
|
3296
|
-
}
|
|
3297
|
-
async function readIfExists(absolutePath) {
|
|
3298
|
-
try {
|
|
3299
|
-
return await readFile4(absolutePath, "utf8");
|
|
3300
|
-
} catch (error) {
|
|
3301
|
-
if (isRecord8(error) && error.code === "ENOENT") {
|
|
3302
|
-
return void 0;
|
|
3303
|
-
}
|
|
3304
|
-
throw error;
|
|
3305
|
-
}
|
|
3306
|
-
}
|
|
3307
|
-
async function assertInsideRepository(root, relativePath) {
|
|
3308
|
-
const absolutePath = join2(root, relativePath);
|
|
3309
|
-
const realRoot = await realpath(root);
|
|
3310
|
-
let realParent;
|
|
3311
|
-
try {
|
|
3312
|
-
realParent = await realpath(dirname2(absolutePath));
|
|
3313
|
-
} catch (error) {
|
|
3314
|
-
if (isRecord8(error) && error.code === "ENOENT") {
|
|
3315
|
-
return;
|
|
3316
|
-
}
|
|
3317
|
-
throw error;
|
|
3318
|
-
}
|
|
3319
|
-
if (realParent !== realRoot && !realParent.startsWith(realRoot + sep)) {
|
|
3320
|
-
throw new Error(
|
|
3321
|
-
`${relativePath} resolves outside the repository (${realParent}); refusing to touch it.`
|
|
3322
|
-
);
|
|
3323
|
-
}
|
|
3324
|
-
try {
|
|
3325
|
-
if ((await lstat(absolutePath)).isSymbolicLink()) {
|
|
3326
|
-
throw new Error(`${relativePath} is a symbolic link; refusing to write through it.`);
|
|
3327
|
-
}
|
|
3328
|
-
} catch (error) {
|
|
3329
|
-
if (!isRecord8(error) || error.code !== "ENOENT") {
|
|
3330
|
-
throw error;
|
|
3331
|
-
}
|
|
3332
|
-
}
|
|
3333
|
-
}
|
|
3334
3868
|
async function applyPlan(root, plan, dryRun, snapshot, recordFirst = true) {
|
|
3335
3869
|
for (const write of plan.writes) {
|
|
3336
3870
|
await assertInsideRepository(root, write.path);
|
|
@@ -3345,9 +3879,9 @@ async function applyPlan(root, plan, dryRun, snapshot, recordFirst = true) {
|
|
|
3345
3879
|
}
|
|
3346
3880
|
const applied = [];
|
|
3347
3881
|
try {
|
|
3348
|
-
const
|
|
3349
|
-
const recordWrites = plan.writes.filter((write) =>
|
|
3350
|
-
const recordDeletes = plan.deletes.filter((path) =>
|
|
3882
|
+
const isRecord10 = (path) => !recordFirst && path === HARNESS_RECORD_PATH;
|
|
3883
|
+
const recordWrites = plan.writes.filter((write) => isRecord10(write.path));
|
|
3884
|
+
const recordDeletes = plan.deletes.filter((path) => isRecord10(path));
|
|
3351
3885
|
const createdDirectories = [...plan.record?.createdDirectories ?? []];
|
|
3352
3886
|
const rememberDirectories = (directories) => {
|
|
3353
3887
|
for (const directory of directories) {
|
|
@@ -3383,14 +3917,14 @@ async function applyPlan(root, plan, dryRun, snapshot, recordFirst = true) {
|
|
|
3383
3917
|
}
|
|
3384
3918
|
for (const path of deletes) {
|
|
3385
3919
|
await assertUnchangedSinceSnapshot(root, path, snapshot);
|
|
3386
|
-
await rm2(
|
|
3920
|
+
await rm2(join3(root, path), { force: true });
|
|
3387
3921
|
applied.push(path);
|
|
3388
3922
|
}
|
|
3389
3923
|
};
|
|
3390
3924
|
try {
|
|
3391
3925
|
await apply(
|
|
3392
|
-
orderedWrites(plan.writes.filter((write) => !
|
|
3393
|
-
plan.deletes.filter((path) => !
|
|
3926
|
+
orderedWrites(plan.writes.filter((write) => !isRecord10(write.path)), recordFirst),
|
|
3927
|
+
plan.deletes.filter((path) => !isRecord10(path))
|
|
3394
3928
|
);
|
|
3395
3929
|
await recordDirectoriesOrExplain(false);
|
|
3396
3930
|
await apply(recordWrites, recordDeletes);
|
|
@@ -3406,7 +3940,7 @@ Already applied before this failure: ${applied.join(", ") || "(nothing)"}`
|
|
|
3406
3940
|
);
|
|
3407
3941
|
}
|
|
3408
3942
|
for (const directory of plan.removableDirectories ?? []) {
|
|
3409
|
-
await rmdir(
|
|
3943
|
+
await rmdir(join3(root, directory)).catch(() => void 0);
|
|
3410
3944
|
}
|
|
3411
3945
|
}
|
|
3412
3946
|
function orderedWrites(writes, recordFirst) {
|
|
@@ -3418,7 +3952,7 @@ async function assertUnchangedSinceSnapshot(root, relativePath, snapshot) {
|
|
|
3418
3952
|
if (!snapshot.has(relativePath)) {
|
|
3419
3953
|
return;
|
|
3420
3954
|
}
|
|
3421
|
-
const now = await readIfExists(
|
|
3955
|
+
const now = await readIfExists(join3(root, relativePath));
|
|
3422
3956
|
if (now !== snapshot.get(relativePath)) {
|
|
3423
3957
|
throw new Error(
|
|
3424
3958
|
`${relativePath} changed while init was running; nothing was written. Run init again.`
|
|
@@ -3426,8 +3960,8 @@ async function assertUnchangedSinceSnapshot(root, relativePath, snapshot) {
|
|
|
3426
3960
|
}
|
|
3427
3961
|
}
|
|
3428
3962
|
async function writeManagedFile(root, write, onDirectoriesCreated) {
|
|
3429
|
-
const absolutePath =
|
|
3430
|
-
const targetDirectory =
|
|
3963
|
+
const absolutePath = join3(root, write.path);
|
|
3964
|
+
const targetDirectory = dirname3(absolutePath);
|
|
3431
3965
|
const firstCreated = await mkdir2(targetDirectory, { recursive: true });
|
|
3432
3966
|
const created = createdChain(root, firstCreated, targetDirectory);
|
|
3433
3967
|
onDirectoriesCreated?.(created);
|
|
@@ -3444,14 +3978,14 @@ function createdChain(root, firstCreated, targetDirectory) {
|
|
|
3444
3978
|
if (firstCreated === void 0) {
|
|
3445
3979
|
return [];
|
|
3446
3980
|
}
|
|
3447
|
-
const stop =
|
|
3981
|
+
const stop = resolve2(firstCreated);
|
|
3448
3982
|
const made = [];
|
|
3449
|
-
for (let at =
|
|
3983
|
+
for (let at = resolve2(targetDirectory); ; at = dirname3(at)) {
|
|
3450
3984
|
const asRelative = relative(root, at);
|
|
3451
3985
|
if (asRelative && !asRelative.startsWith("..")) {
|
|
3452
|
-
made.push(asRelative.split(
|
|
3986
|
+
made.push(asRelative.split(sep2).join("/"));
|
|
3453
3987
|
}
|
|
3454
|
-
if (at === stop ||
|
|
3988
|
+
if (at === stop || dirname3(at) === at) {
|
|
3455
3989
|
break;
|
|
3456
3990
|
}
|
|
3457
3991
|
}
|
|
@@ -3461,7 +3995,7 @@ async function fileMode(absolutePath) {
|
|
|
3461
3995
|
try {
|
|
3462
3996
|
return (await stat3(absolutePath)).mode & 4095;
|
|
3463
3997
|
} catch (error) {
|
|
3464
|
-
if (
|
|
3998
|
+
if (isRecord9(error) && error.code === "ENOENT") {
|
|
3465
3999
|
return void 0;
|
|
3466
4000
|
}
|
|
3467
4001
|
throw error;
|
|
@@ -3499,43 +4033,53 @@ function requireValue3(flag, value) {
|
|
|
3499
4033
|
}
|
|
3500
4034
|
function basenameOf(path) {
|
|
3501
4035
|
const parts = path.split(/[\\/]/).filter(Boolean);
|
|
3502
|
-
return parts[parts.length - 1] ?? "
|
|
4036
|
+
return parts[parts.length - 1] ?? "ainecto-project";
|
|
3503
4037
|
}
|
|
3504
|
-
function
|
|
4038
|
+
function isRecord9(value) {
|
|
3505
4039
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3506
4040
|
}
|
|
3507
4041
|
|
|
3508
|
-
// src/core/version.ts
|
|
3509
|
-
var CLI_VERSION = true ? "0.3.0" : "dev";
|
|
3510
|
-
var CLI_USER_AGENT = `@ai-erd/mcp ${CLI_VERSION}`;
|
|
3511
|
-
|
|
3512
4042
|
// src/adapters/cli/ainectoCli.ts
|
|
3513
4043
|
async function runAinectoCli(argv, io) {
|
|
3514
|
-
let parsed = { json: argv.includes("--json"), help: false, positionals: [] };
|
|
4044
|
+
let parsed = { json: argv.includes("--json"), help: false, version: false, positionals: [] };
|
|
3515
4045
|
try {
|
|
3516
4046
|
parsed = parseGlobalArgs(argv);
|
|
4047
|
+
if (parsed.version || parsed.positionals[0] === "version") {
|
|
4048
|
+
io.stdout.write(renderSuccess({ version: CLI_VERSION }, { json: parsed.json }));
|
|
4049
|
+
return 0;
|
|
4050
|
+
}
|
|
3517
4051
|
if (parsed.help || parsed.positionals.length === 0) {
|
|
3518
4052
|
io.stdout.write(helpText());
|
|
3519
4053
|
return 0;
|
|
3520
4054
|
}
|
|
3521
4055
|
const resolved = resolveEndpoint({ env: parsed.env, endpoint: parsed.endpoint });
|
|
3522
|
-
|
|
3523
|
-
if (parsed.positionals[0] === "init" && sessionRole === void 0 && !argv.includes("--undo")) {
|
|
3524
|
-
sessionRole = await readRepositoryRole(process.cwd());
|
|
3525
|
-
}
|
|
4056
|
+
const explicitRole = () => resolveRole({ role: parsed.role });
|
|
3526
4057
|
const tokenStore = new FileTokenStore();
|
|
3527
|
-
const
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
4058
|
+
const connect = (role) => {
|
|
4059
|
+
const auth = new OAuthClient({
|
|
4060
|
+
endpoint: resolved.endpoint,
|
|
4061
|
+
tokenStore,
|
|
4062
|
+
// ★init --dry-run 은 토큰을 갱신하지도 않는다 — 「아무것도 바꾸지 않는다」에 로그인 상태도 든다.
|
|
4063
|
+
// (init 의 플래그는 positional 로 흘러온다.)
|
|
4064
|
+
readOnly: parsed.positionals[0] === "init" && parsed.positionals.includes("--dry-run"),
|
|
4065
|
+
// ★URL 을 «브라우저를 열기 전에» 알린다. stdout 은 결과 전용이라 stderr 에 쓴다.
|
|
4066
|
+
// --json 이면 JSON 한 줄이다(설계 §21-3 (가)). 로그인이 역할과 무관해져 role 칸은 없다(0.4.2 §2-5).
|
|
4067
|
+
onAuthorizeUrl: (url) => {
|
|
4068
|
+
io.stderr.write(parsed.json ? `${JSON.stringify({ event: "login_url", url })}
|
|
4069
|
+
` : `Opening your browser to sign in to AiNecto. If it does not open, visit:
|
|
4070
|
+
${url}
|
|
4071
|
+
`);
|
|
4072
|
+
}
|
|
4073
|
+
});
|
|
4074
|
+
const client2 = new McpRpcClient({ endpoint: resolved.endpoint, tokenProvider: auth, role });
|
|
4075
|
+
return { auth, client: client2 };
|
|
4076
|
+
};
|
|
3533
4077
|
const [domain, action, ...rest] = parsed.positionals;
|
|
3534
4078
|
if (domain === "mcp") {
|
|
3535
4079
|
await runConnector({
|
|
3536
4080
|
endpoint: resolved.endpoint,
|
|
3537
|
-
//
|
|
3538
|
-
role:
|
|
4081
|
+
// 브리지의 역할은 인자·환경변수에서만 온다 — bin/mcp.ts 와 같은 규칙. 저장소를 적용하지 않는다.
|
|
4082
|
+
role: explicitRole().role ?? null,
|
|
3539
4083
|
input: io.stdin,
|
|
3540
4084
|
output: io.stdout,
|
|
3541
4085
|
errorOutput: io.stderr
|
|
@@ -3545,22 +4089,69 @@ async function runAinectoCli(argv, io) {
|
|
|
3545
4089
|
if (domain === "init") {
|
|
3546
4090
|
return await executeInitCommand({
|
|
3547
4091
|
argv: parsed.positionals.slice(1),
|
|
3548
|
-
role:
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
4092
|
+
role: explicitRole().role,
|
|
4093
|
+
connect: (role) => {
|
|
4094
|
+
const { auth, client: client2 } = connect(role);
|
|
4095
|
+
return {
|
|
4096
|
+
client: client2,
|
|
4097
|
+
// 문서는 인증 후에 받는다 — 토큰이 없으면 패키지 기본값으로 가고, 그 사실을 보고한다.
|
|
4098
|
+
// ★«필요할 때» 가져온다. --undo 는 로컬 작업이라 토큰을 건드릴 이유가 없다(S1) — undo 는
|
|
4099
|
+
// connect 자체를 부르지 않는다.
|
|
4100
|
+
// ★갱신 실패는 «토큰 없음»(→ 로그인)이지만, AINECTO_TOKEN 을 못 쓰는 주소라는 거절은 삼키지 않는다 —
|
|
4101
|
+
// 삼키면 그 설정 오류가 브라우저 로그인으로 둔갑한다.
|
|
4102
|
+
accessToken: async () => {
|
|
4103
|
+
try {
|
|
4104
|
+
return await auth.getAccessToken();
|
|
4105
|
+
} catch (error) {
|
|
4106
|
+
if (process.env.AINECTO_TOKEN) {
|
|
4107
|
+
throw error;
|
|
4108
|
+
}
|
|
4109
|
+
return void 0;
|
|
4110
|
+
}
|
|
4111
|
+
},
|
|
4112
|
+
// 로그인한다 — `ainecto auth login` 과 같은 함수다. 서버마다 한 번이다(0.4.2).
|
|
4113
|
+
login: async () => {
|
|
4114
|
+
await auth.login();
|
|
4115
|
+
}
|
|
4116
|
+
};
|
|
4117
|
+
},
|
|
4118
|
+
// ★대화형일 때만 묻는다(판정은 isInteractive 한 곳). 에이전트·CI 는 TTY 가 아니다.
|
|
4119
|
+
prompter: isInteractive(io, parsed.json) ? createReadlinePrompter(io) : void 0,
|
|
3553
4120
|
endpoint: resolved.endpoint,
|
|
3554
4121
|
env: resolved.env,
|
|
3555
|
-
cliVersion:
|
|
4122
|
+
cliVersion: `${PACKAGE_NAME} ${CLI_VERSION}`,
|
|
3556
4123
|
cwd: process.cwd(),
|
|
3557
4124
|
json: parsed.json,
|
|
3558
4125
|
io
|
|
3559
4126
|
});
|
|
3560
4127
|
}
|
|
3561
4128
|
if (domain === "auth") {
|
|
3562
|
-
|
|
4129
|
+
if (parsed.role !== void 0) {
|
|
4130
|
+
io.stderr.write("Sign-in is one per machine; --role is not used by auth.\n");
|
|
4131
|
+
}
|
|
4132
|
+
return await handleAuth(action, connect(null).auth, resolved.endpoint, parsed.json, io);
|
|
4133
|
+
}
|
|
4134
|
+
const env = parsed.env ?? "prod";
|
|
4135
|
+
if (domain === "tools" && action === "catalog") {
|
|
4136
|
+
return await printCatalog(parsed, io, resolved.endpoint, async () => {
|
|
4137
|
+
const explicit2 = explicitRole();
|
|
4138
|
+
return connect(await commandRole(explicit2, process.cwd())).client;
|
|
4139
|
+
});
|
|
4140
|
+
}
|
|
4141
|
+
let routeTools = getGeneratedTools(env);
|
|
4142
|
+
let remote = isAttachmentsUploadCommand(domain, action) || domain === "tools" && (action === "list" || action === "call") || matchGeneratedCommand(routeTools, parsed.positionals) !== void 0;
|
|
4143
|
+
if (!remote) {
|
|
4144
|
+
const live = await loadLiveTools(env, connect(null).client).catch(() => void 0);
|
|
4145
|
+
if (live && matchGeneratedCommand(live, parsed.positionals)) {
|
|
4146
|
+
routeTools = live;
|
|
4147
|
+
remote = true;
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
if (!remote) {
|
|
4151
|
+
throw new Error(domain === "tools" ? "Expected tools list, tools call <mcpName>, or tools catalog." : `Unknown command "${domain}".`);
|
|
3563
4152
|
}
|
|
4153
|
+
const explicit = explicitRole();
|
|
4154
|
+
const { client } = connect(await commandRole(explicit, process.cwd()));
|
|
3564
4155
|
if (isAttachmentsUploadCommand(domain, action)) {
|
|
3565
4156
|
return await executeAttachmentsUploadCommand({
|
|
3566
4157
|
argv: rest,
|
|
@@ -3570,7 +4161,7 @@ async function runAinectoCli(argv, io) {
|
|
|
3570
4161
|
});
|
|
3571
4162
|
}
|
|
3572
4163
|
const generatedResult = await executeGeneratedCommand({
|
|
3573
|
-
|
|
4164
|
+
tools: routeTools,
|
|
3574
4165
|
argv: parsed.positionals,
|
|
3575
4166
|
client,
|
|
3576
4167
|
json: parsed.json,
|
|
@@ -3588,15 +4179,32 @@ async function runAinectoCli(argv, io) {
|
|
|
3588
4179
|
return 1;
|
|
3589
4180
|
}
|
|
3590
4181
|
}
|
|
4182
|
+
async function commandRole(explicit, cwd) {
|
|
4183
|
+
const explicitRole = explicit.role ?? null;
|
|
4184
|
+
const found = await findRepositoryRole(cwd);
|
|
4185
|
+
if (found.kind === "conflict") {
|
|
4186
|
+
throw new Error(await askTheUserToFixRole(found.dir, found.reason));
|
|
4187
|
+
}
|
|
4188
|
+
if (found.kind === "none") {
|
|
4189
|
+
return explicitRole;
|
|
4190
|
+
}
|
|
4191
|
+
if (explicitRole !== null && explicitRole !== found.role) {
|
|
4192
|
+
const given = explicit.source === "env" ? `${ROLE_ENV_VAR}=${explicitRole}` : `--role ${explicitRole}`;
|
|
4193
|
+
throw new Error(
|
|
4194
|
+
`This repository's AI sessions have the role ${found.role} (set in ${found.dir}). ${given} does not match \u2014 the role changes only with \`ainecto init --role <role>\`, after asking the user.`
|
|
4195
|
+
);
|
|
4196
|
+
}
|
|
4197
|
+
return found.role;
|
|
4198
|
+
}
|
|
3591
4199
|
async function handleAuth(action, auth, endpoint, json, io) {
|
|
3592
4200
|
if (action === "login") {
|
|
3593
4201
|
await auth.login();
|
|
3594
|
-
io.stdout.write(renderSuccess({ endpoint, authenticated: true }, { json }));
|
|
4202
|
+
io.stdout.write(renderSuccess({ ...await nextStep(), endpoint, authenticated: true }, { json }));
|
|
3595
4203
|
return 0;
|
|
3596
4204
|
}
|
|
3597
4205
|
if (action === "status") {
|
|
3598
4206
|
const token = await auth.getAccessToken();
|
|
3599
|
-
io.stdout.write(renderSuccess({ endpoint, authenticated: Boolean(token) }, { json }));
|
|
4207
|
+
io.stdout.write(renderSuccess({ ...await nextStep(), endpoint, authenticated: Boolean(token) }, { json }));
|
|
3600
4208
|
return 0;
|
|
3601
4209
|
}
|
|
3602
4210
|
if (action === "logout") {
|
|
@@ -3606,6 +4214,20 @@ async function handleAuth(action, auth, endpoint, json, io) {
|
|
|
3606
4214
|
}
|
|
3607
4215
|
throw new Error("Expected auth login, auth status, or auth logout.");
|
|
3608
4216
|
}
|
|
4217
|
+
async function printCatalog(parsed, io, endpoint, liveClient) {
|
|
4218
|
+
const catalogEnv = parsed.env ?? "prod";
|
|
4219
|
+
try {
|
|
4220
|
+
const tools = await loadLiveTools(catalogEnv, await liveClient());
|
|
4221
|
+
io.stdout.write(renderSuccess(tools, { json: parsed.json }));
|
|
4222
|
+
} catch (error) {
|
|
4223
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
4224
|
+
io.stdout.write(renderSuccess(getGeneratedTools(catalogEnv), {
|
|
4225
|
+
json: parsed.json,
|
|
4226
|
+
warnings: [`Could not read the live tool list from ${endpoint} (${reason}). Showing the ${catalogEnv} catalog checked in when this CLI version was built \u2014 a snapshot of that server's tools/list that may miss newer document types and tools. Run \`ainecto auth login\`, then retry.`]
|
|
4227
|
+
}));
|
|
4228
|
+
}
|
|
4229
|
+
return 0;
|
|
4230
|
+
}
|
|
3609
4231
|
async function handleTools(action, rest, parsed, client, io) {
|
|
3610
4232
|
if (action === "list") {
|
|
3611
4233
|
const tools = await client.toolsList();
|
|
@@ -3628,18 +4250,10 @@ async function handleTools(action, rest, parsed, client, io) {
|
|
|
3628
4250
|
io.stdout.write(renderSuccess(result, { json: parsed.json, warnings: payload.warnings }));
|
|
3629
4251
|
return isToolError(result) ? 1 : 0;
|
|
3630
4252
|
}
|
|
3631
|
-
if (action === "catalog") {
|
|
3632
|
-
const catalogEnv = parsed.env ?? "prod";
|
|
3633
|
-
io.stdout.write(renderSuccess(getGeneratedTools(parsed.env ?? "prod"), {
|
|
3634
|
-
json: parsed.json,
|
|
3635
|
-
warnings: [catalogEnv === "dev" ? "Local dev catalog is a checked-in live sync snapshot; rerun sync:tools --env dev to refresh it." : "Local prod catalog is seed fixture only until authenticated prod live sync updates generated catalogs."]
|
|
3636
|
-
}));
|
|
3637
|
-
return 0;
|
|
3638
|
-
}
|
|
3639
4253
|
throw new Error("Expected tools list, tools call <mcpName>, or tools catalog.");
|
|
3640
4254
|
}
|
|
3641
4255
|
function parseGlobalArgs(argv) {
|
|
3642
|
-
const result = { json: false, help: false, positionals: [] };
|
|
4256
|
+
const result = { json: false, help: false, version: false, positionals: [] };
|
|
3643
4257
|
for (let index = 0; index < argv.length; index += 1) {
|
|
3644
4258
|
const arg = argv[index];
|
|
3645
4259
|
if (!arg) {
|
|
@@ -3655,6 +4269,8 @@ function parseGlobalArgs(argv) {
|
|
|
3655
4269
|
result.json = true;
|
|
3656
4270
|
} else if (arg === "--help" || arg === "-h") {
|
|
3657
4271
|
result.help = true;
|
|
4272
|
+
} else if (arg === "--version" || arg === "-v" || arg === "-V") {
|
|
4273
|
+
result.version = true;
|
|
3658
4274
|
} else {
|
|
3659
4275
|
result.positionals.push(arg);
|
|
3660
4276
|
}
|
|
@@ -3686,13 +4302,25 @@ function requireValue4(flag, value) {
|
|
|
3686
4302
|
}
|
|
3687
4303
|
return value;
|
|
3688
4304
|
}
|
|
4305
|
+
async function nextStep() {
|
|
4306
|
+
const found = await findRepositoryRole(process.cwd());
|
|
4307
|
+
return found.kind === "none" ? { setup: "incomplete", next: NEXT_STEP_INIT } : {};
|
|
4308
|
+
}
|
|
4309
|
+
var NEXT_STEP_INIT = [
|
|
4310
|
+
"AiNecto setup is not finished until `npx -y ainecto@latest init` runs in the repository.",
|
|
4311
|
+
"AI agents: tell the user this now, even if they only asked to install, update, or sign in. Then ask",
|
|
4312
|
+
"which role this repository's AI sessions should have \u2014 Design, Development, Test, or Validation \u2014",
|
|
4313
|
+
"never pick one yourself, and run `npx -y ainecto@latest init --role <role>` from the repository root.",
|
|
4314
|
+
"In a terminal: `npx -y ainecto@latest init` asks for the role and the project."
|
|
4315
|
+
].join("\n");
|
|
3689
4316
|
function helpText() {
|
|
3690
4317
|
return [
|
|
3691
|
-
"
|
|
4318
|
+
"AiNecto CLI",
|
|
4319
|
+
"",
|
|
4320
|
+
NEXT_STEP_INIT,
|
|
3692
4321
|
"",
|
|
3693
4322
|
"Usage:",
|
|
3694
|
-
" ainecto auth login
|
|
3695
|
-
" ainecto auth status|logout [--role <role>] [--env prod|dev] [--json]",
|
|
4323
|
+
" ainecto auth login|status|logout [--env prod|dev] [--json] (one sign-in per machine)",
|
|
3696
4324
|
" ainecto tools list [--env prod|dev] [--endpoint URL] [--json]",
|
|
3697
4325
|
" ainecto tools call <mcpName> [-f payload.json|@-] [inline-json] [--json]",
|
|
3698
4326
|
" ainecto attachments upload --document-uuid <uuid> <file...> [--json]",
|
|
@@ -3700,8 +4328,9 @@ function helpText() {
|
|
|
3700
4328
|
" ainecto mcp [--env prod|dev] [--endpoint URL] [--role design|development|test|validation]",
|
|
3701
4329
|
"",
|
|
3702
4330
|
"Harness:",
|
|
3703
|
-
"
|
|
3704
|
-
"
|
|
4331
|
+
" ainecto init [--role <design|development|test|validation>] [--project <uuid>] [--dry-run]",
|
|
4332
|
+
" In a terminal, asks for the role and project when they are not given.",
|
|
4333
|
+
" ainecto init --undo",
|
|
3705
4334
|
""
|
|
3706
4335
|
].join("\n");
|
|
3707
4336
|
}
|