@codai/axiom-mcp 2.1.0 → 2.2.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 +56 -24
- package/dist/{axm-lazy-CR-FmFVd.js → axm-lazy-LAnrfjUO.js} +32 -3
- package/dist/axm-lazy-Xin1nh7Q.js +6284 -0
- package/dist/axm-lazy.js +119 -9
- package/dist/cli-main.js +1650 -14959
- package/dist/dist-Bt9Wvd_P.js +6379 -0
- package/dist/{dist-FFqnyzCe.js → dist-D4M2Lyji.js} +264 -27
- package/dist/gate-lazy.js +581 -46
- package/dist/gc-lazy.js +6885 -0
- package/dist/http-lazy.js +18262 -9889
- package/dist/index.d.ts +140 -4
- package/dist/index.js +575 -41
- package/dist/mcp-lazy.js +23050 -0
- package/dist/migrate-lazy.js +48 -7
- package/dist/verify-tree-lazy.js +6893 -0
- package/package.json +14 -9
- package/spec/codai-tools.json +283 -0
- package/spec/tools.json +1487 -160
package/dist/index.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { ApplyResultSchema, AxiomError, CheckReportSchema, DigestRefSchema, ErrorCodeSchema, JournalPhaseSchema, JournalSchema, ManifestBodySchema, ManifestBundleSchema, PlanSchema, ProfileSchema, RepoSnapshotSchema, TrustStateSchema, TrustStoreSchema, compareUtf8, isValidRelPath } from "@codai/axiom-schema";
|
|
2
|
+
import { ApplyResultSchema, AxiomError, CheckReportSchema, DigestRefSchema, ErrorCodeSchema, JournalPhaseSchema, JournalSchema, ManifestBodySchema, ManifestBundleSchema, PlanArtifactSchema, PlanSchema, ProfileSchema, RepoSnapshotSchema, TrustStateSchema, TrustStoreSchema, compareUtf8, isValidRelPath } from "@codai/axiom-schema";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { createReadStream, realpath } from "node:fs";
|
|
5
5
|
import { access, chmod, constants, lstat, mkdir, opendir, readFile, readdir, realpath as realpath$1, rename, stat, writeFile } from "node:fs/promises";
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
|
-
import { TRUST_FILE_DEFAULT, TRUST_STATE_FILE, loadProfile, runChecks, verifyBundleSignatures } from "@codai/axiom-checks";
|
|
9
|
-
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/
|
|
8
|
+
import { TRUST_FILE_DEFAULT, TRUST_STATE_FILE, TRUST_STATE_KEY_FILE, loadProfile, runChecks, trustStateMac, trustStateMacOk, verifyBundleSignatures } from "@codai/axiom-checks";
|
|
9
|
+
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/server";
|
|
10
|
+
import "@modelcontextprotocol/server/stdio";
|
|
10
11
|
import { webEmitter } from "@codai/axiom-emitters-web";
|
|
11
12
|
import { compilePlan, createEmitterRegistry, diffManifests, verifyBundle } from "@codai/axiom-plan";
|
|
12
13
|
import { appliedPath, apply, rollback } from "@codai/axiom-apply";
|
|
14
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
13
15
|
import { canonicalDigestRef } from "@codai/axiom-canon";
|
|
14
|
-
import { createHash } from "node:crypto";
|
|
15
16
|
//#region src/jsonschema.ts
|
|
16
17
|
const SCHEMA_KINDS = [
|
|
17
18
|
"Plan",
|
|
@@ -167,6 +168,53 @@ async function resolveRoot(policy, requested) {
|
|
|
167
168
|
} });
|
|
168
169
|
}
|
|
169
170
|
//#endregion
|
|
171
|
+
//#region src/adapter.ts
|
|
172
|
+
/**
|
|
173
|
+
* MCP SDK adapter (v2-architecture §5.4, D-19, S-405).
|
|
174
|
+
*
|
|
175
|
+
* The ONLY module under `packages/mcp/src` allowed to import `@modelcontextprotocol/*`
|
|
176
|
+
* (guard: `check-sdk-adapter`). Everything AXIOM needs from the SDK — server construction,
|
|
177
|
+
* the two serving entries, the result types — is re-exported here under names that do not
|
|
178
|
+
* change when the SDK does, so the next SDK major is a one-file diff.
|
|
179
|
+
*
|
|
180
|
+
* SDK 2.0.0 (2026-07-27) facts this seam encodes:
|
|
181
|
+
* - `@modelcontextprotocol/sdk` is split; the server surface is `@modelcontextprotocol/server`
|
|
182
|
+
* and stdio lives on its `./stdio` subpath (the root barrel is runtime-neutral).
|
|
183
|
+
* - A hand-connected `McpServer` + `StdioServerTransport` speaks ONLY the 2025 era. Serving the
|
|
184
|
+
* 2026-07-28 revision goes through the factory entries `serveStdio` / `createMcpHandler`,
|
|
185
|
+
* which pin one instance per connection (stdio) or build one per request (HTTP) and pass the
|
|
186
|
+
* factory the `era` they are about to serve.
|
|
187
|
+
* - `ttlMs` / `cacheScope` (SEP-2549) are stamped by the SDK on 2026-era cacheable results from
|
|
188
|
+
* `ServerOptions.cacheHints`; 2025-era responses never carry them.
|
|
189
|
+
*/
|
|
190
|
+
/**
|
|
191
|
+
* Cache policy AXIOM advertises on 2026-era `tools/list` & co. The tool catalogue is static
|
|
192
|
+
* for the life of a process, so a shared 5-minute TTL is safe; `resources/read` is content
|
|
193
|
+
* addressed (`axiom://manifest/<sha>`) and therefore immutable → public, 1 day.
|
|
194
|
+
*/
|
|
195
|
+
const CACHE_HINTS = {
|
|
196
|
+
"tools/list": {
|
|
197
|
+
ttlMs: 3e5,
|
|
198
|
+
cacheScope: "public"
|
|
199
|
+
},
|
|
200
|
+
"resources/templates/list": {
|
|
201
|
+
ttlMs: 3e5,
|
|
202
|
+
cacheScope: "public"
|
|
203
|
+
},
|
|
204
|
+
"server/discover": {
|
|
205
|
+
ttlMs: 3e5,
|
|
206
|
+
cacheScope: "public"
|
|
207
|
+
},
|
|
208
|
+
"resources/list": {
|
|
209
|
+
ttlMs: 1e4,
|
|
210
|
+
cacheScope: "private"
|
|
211
|
+
},
|
|
212
|
+
"resources/read": {
|
|
213
|
+
ttlMs: 864e5,
|
|
214
|
+
cacheScope: "public"
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
//#endregion
|
|
170
218
|
//#region src/emitters.ts
|
|
171
219
|
/** Template emitters available to `axiom_plan_compile` / `axiom compile` (D-13: optional sugar). */
|
|
172
220
|
const EMITTERS = createEmitterRegistry([webEmitter]);
|
|
@@ -266,6 +314,251 @@ async function listStored(roots, sub) {
|
|
|
266
314
|
return out;
|
|
267
315
|
}
|
|
268
316
|
//#endregion
|
|
317
|
+
//#region src/tasks.ts
|
|
318
|
+
/**
|
|
319
|
+
* S-406 / D-24 — tool-level tasks and chunked plan sessions.
|
|
320
|
+
*
|
|
321
|
+
* MCP SDK v2 ships the `io.modelcontextprotocol/tasks` wire vocabulary but no runtime (the
|
|
322
|
+
* `tasks/*` methods are excluded from `setRequestHandler`, and the 2026-07-28 `tools/call` codec
|
|
323
|
+
* rejects a `CreateTaskResult`). AXIOM therefore models long-running work as ordinary tools:
|
|
324
|
+
* `axiom_check_start` → `{ taskId, status: "working", pollIntervalMs, ttlMs }`, then
|
|
325
|
+
* `axiom_task_get` until `status` is terminal, `axiom_task_cancel` to abort. This works on every
|
|
326
|
+
* client that can call a tool (Copilot, codai agent-core, SDK v1 and v2) and on both wire eras.
|
|
327
|
+
*
|
|
328
|
+
* State is per factory (shared by every server instance a `serverFactory` builds, like
|
|
329
|
+
* `seenRoots`), never persisted: a restarted server has no tasks, and a poll for an unknown id is
|
|
330
|
+
* `ERR_TASK_NOT_FOUND`. Terminal tasks are kept for `ttlMs` after finishing, then dropped.
|
|
331
|
+
*
|
|
332
|
+
* The same store hosts chunked plan sessions (`axiom_plan_begin` → `axiom_plan_add`* →
|
|
333
|
+
* `axiom_plan_seal`): artifacts accumulate server-side so a Plan whose JSON would exceed the
|
|
334
|
+
* per-call 4 MiB payload cap can still be compiled. Sealing feeds the assembled Plan to the very
|
|
335
|
+
* same `compilePlan`, so the digest is identical to a one-shot compile (property test).
|
|
336
|
+
*/
|
|
337
|
+
const TASK_STATUSES = [
|
|
338
|
+
"working",
|
|
339
|
+
"completed",
|
|
340
|
+
"failed",
|
|
341
|
+
"cancelled"
|
|
342
|
+
];
|
|
343
|
+
/** How long a finished task stays pollable. */
|
|
344
|
+
const TASK_TTL_MS = 6e5;
|
|
345
|
+
/** Advisory poll interval returned to clients. */
|
|
346
|
+
const TASK_POLL_INTERVAL_MS = 2e3;
|
|
347
|
+
/** Concurrent `working` tasks per server process; the (n+1)th `axiom_check_start` is refused. */
|
|
348
|
+
const TASK_MAX_WORKING = 8;
|
|
349
|
+
/** Idle sessions (no `add`/`seal`) are dropped after this. */
|
|
350
|
+
const PLAN_SESSION_TTL_MS = 18e5;
|
|
351
|
+
/** Artifacts a session may hold — same bound as `PlanSchema.artifacts.max`. */
|
|
352
|
+
const PLAN_SESSION_MAX_ARTIFACTS = 2e3;
|
|
353
|
+
/** Summed UTF-8 JSON bytes of every chunk a session accepts (64 MiB, the `default` profile's `maxTotalBytes`). */
|
|
354
|
+
const PLAN_SESSION_MAX_BYTES = 67108864;
|
|
355
|
+
const TaskStatusSchema = z.enum(TASK_STATUSES);
|
|
356
|
+
const TaskErrorSchema = z.object({
|
|
357
|
+
code: z.string(),
|
|
358
|
+
message: z.string(),
|
|
359
|
+
details: z.record(z.string(), z.unknown()).optional()
|
|
360
|
+
});
|
|
361
|
+
var TaskStore = class {
|
|
362
|
+
tasks = /* @__PURE__ */ new Map();
|
|
363
|
+
now;
|
|
364
|
+
ttlMs;
|
|
365
|
+
pollIntervalMs;
|
|
366
|
+
maxWorking;
|
|
367
|
+
constructor(opts = {}) {
|
|
368
|
+
this.now = opts.now ?? Date.now;
|
|
369
|
+
this.ttlMs = opts.ttlMs ?? 6e5;
|
|
370
|
+
this.pollIntervalMs = opts.pollIntervalMs ?? 2e3;
|
|
371
|
+
this.maxWorking = opts.maxWorking ?? 8;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Start `work` in the background. The returned record is `working` until the promise settles;
|
|
375
|
+
* a rejection becomes `failed` (AxiomError → its closed code, anything else → `ERR_INTERNAL`),
|
|
376
|
+
* a rejection after `cancel()` stays `cancelled`.
|
|
377
|
+
*/
|
|
378
|
+
start(tool, work, meta = {}) {
|
|
379
|
+
this.sweep();
|
|
380
|
+
const working = [...this.tasks.values()].filter((t) => t.status === "working").length;
|
|
381
|
+
if (working >= this.maxWorking) throw new AxiomError("ERR_EBUSY", `too many running tasks (${working}/${this.maxWorking})`, { details: {
|
|
382
|
+
working,
|
|
383
|
+
max: this.maxWorking
|
|
384
|
+
} });
|
|
385
|
+
const controller = new AbortController();
|
|
386
|
+
const rec = {
|
|
387
|
+
taskId: randomUUID(),
|
|
388
|
+
tool,
|
|
389
|
+
status: "working",
|
|
390
|
+
createdAt: this.now(),
|
|
391
|
+
controller
|
|
392
|
+
};
|
|
393
|
+
if (meta.root !== void 0) rec.root = meta.root;
|
|
394
|
+
this.tasks.set(rec.taskId, rec);
|
|
395
|
+
work(controller.signal).then((result) => {
|
|
396
|
+
if (rec.status !== "working") return;
|
|
397
|
+
rec.status = "completed";
|
|
398
|
+
rec.result = result;
|
|
399
|
+
rec.finishedAt = this.now();
|
|
400
|
+
}, (err) => {
|
|
401
|
+
if (rec.status !== "working") return;
|
|
402
|
+
rec.status = controller.signal.aborted ? "cancelled" : "failed";
|
|
403
|
+
rec.error = toTaskError(err);
|
|
404
|
+
rec.finishedAt = this.now();
|
|
405
|
+
});
|
|
406
|
+
return rec;
|
|
407
|
+
}
|
|
408
|
+
get(taskId) {
|
|
409
|
+
this.sweep();
|
|
410
|
+
const rec = this.tasks.get(taskId);
|
|
411
|
+
if (rec === void 0) throw new AxiomError("ERR_TASK_NOT_FOUND", `no task ${taskId}`, { details: { taskId } });
|
|
412
|
+
return rec;
|
|
413
|
+
}
|
|
414
|
+
/** Abort a `working` task; terminal tasks are left as they are (idempotent). */
|
|
415
|
+
cancel(taskId) {
|
|
416
|
+
const rec = this.get(taskId);
|
|
417
|
+
if (rec.status === "working") {
|
|
418
|
+
rec.status = "cancelled";
|
|
419
|
+
rec.finishedAt = this.now();
|
|
420
|
+
rec.error = {
|
|
421
|
+
code: "ERR_TASK_CANCELLED",
|
|
422
|
+
message: "cancelled via axiom_task_cancel"
|
|
423
|
+
};
|
|
424
|
+
rec.controller.abort();
|
|
425
|
+
}
|
|
426
|
+
return rec;
|
|
427
|
+
}
|
|
428
|
+
describe(rec) {
|
|
429
|
+
return {
|
|
430
|
+
taskId: rec.taskId,
|
|
431
|
+
tool: rec.tool,
|
|
432
|
+
status: rec.status,
|
|
433
|
+
pollIntervalMs: this.pollIntervalMs,
|
|
434
|
+
ttlMs: this.ttlMs,
|
|
435
|
+
elapsedMs: Math.max(0, (rec.finishedAt ?? this.now()) - rec.createdAt)
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
/** Drop terminal tasks older than `ttlMs`. */
|
|
439
|
+
sweep() {
|
|
440
|
+
const cutoff = this.now() - this.ttlMs;
|
|
441
|
+
for (const [id, t] of this.tasks) if (t.finishedAt !== void 0 && t.finishedAt < cutoff) this.tasks.delete(id);
|
|
442
|
+
}
|
|
443
|
+
/** Abort everything still running (server shutdown). */
|
|
444
|
+
abortAll() {
|
|
445
|
+
for (const t of this.tasks.values()) if (t.status === "working") this.cancel(t.taskId);
|
|
446
|
+
}
|
|
447
|
+
get size() {
|
|
448
|
+
return this.tasks.size;
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
function toTaskError(err) {
|
|
452
|
+
if (err instanceof AxiomError) {
|
|
453
|
+
const j = err.toJSON();
|
|
454
|
+
const out = {
|
|
455
|
+
code: j.code,
|
|
456
|
+
message: j.message
|
|
457
|
+
};
|
|
458
|
+
if (j.details !== void 0) out.details = j.details;
|
|
459
|
+
return out;
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
code: "ERR_INTERNAL",
|
|
463
|
+
message: err instanceof Error ? err.message : String(err)
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
var PlanSessionStore = class {
|
|
467
|
+
sessions = /* @__PURE__ */ new Map();
|
|
468
|
+
now;
|
|
469
|
+
ttlMs;
|
|
470
|
+
maxOpen;
|
|
471
|
+
maxArtifacts;
|
|
472
|
+
maxBytes;
|
|
473
|
+
constructor(opts = {}) {
|
|
474
|
+
this.now = opts.now ?? Date.now;
|
|
475
|
+
this.ttlMs = opts.ttlMs ?? 18e5;
|
|
476
|
+
this.maxOpen = opts.maxOpen ?? 16;
|
|
477
|
+
this.maxArtifacts = opts.maxArtifacts ?? 2e3;
|
|
478
|
+
this.maxBytes = opts.maxBytes ?? 67108864;
|
|
479
|
+
}
|
|
480
|
+
begin(header) {
|
|
481
|
+
this.sweep();
|
|
482
|
+
if (this.sessions.size >= this.maxOpen) throw new AxiomError("ERR_EBUSY", `too many open plan sessions (${this.sessions.size}/${this.maxOpen})`, { details: {
|
|
483
|
+
open: this.sessions.size,
|
|
484
|
+
max: this.maxOpen
|
|
485
|
+
} });
|
|
486
|
+
const t = this.now();
|
|
487
|
+
const s = {
|
|
488
|
+
sessionId: randomUUID(),
|
|
489
|
+
header,
|
|
490
|
+
artifacts: [],
|
|
491
|
+
paths: /* @__PURE__ */ new Set(),
|
|
492
|
+
bytes: 0,
|
|
493
|
+
createdAt: t,
|
|
494
|
+
touchedAt: t,
|
|
495
|
+
sealed: false
|
|
496
|
+
};
|
|
497
|
+
this.sessions.set(s.sessionId, s);
|
|
498
|
+
return s;
|
|
499
|
+
}
|
|
500
|
+
get(sessionId) {
|
|
501
|
+
this.sweep();
|
|
502
|
+
const s = this.sessions.get(sessionId);
|
|
503
|
+
if (s === void 0) throw new AxiomError("ERR_TASK_NOT_FOUND", `no plan session ${sessionId}`, { details: { sessionId } });
|
|
504
|
+
return s;
|
|
505
|
+
}
|
|
506
|
+
/** Append already-validated artifacts; duplicates (within or across chunks) are `ERR_INVALID_PLAN`. */
|
|
507
|
+
add(sessionId, artifacts, chunkBytes) {
|
|
508
|
+
const s = this.get(sessionId);
|
|
509
|
+
if (s.sealed) throw new AxiomError("ERR_PLAN_SESSION_STATE", "plan session is already sealed", { details: { sessionId } });
|
|
510
|
+
if (s.artifacts.length + artifacts.length > this.maxArtifacts) throw new AxiomError("ERR_PLAN_SESSION_STATE", `session would hold ${s.artifacts.length + artifacts.length} artifacts; max ${this.maxArtifacts}`, { details: {
|
|
511
|
+
sessionId,
|
|
512
|
+
have: s.artifacts.length,
|
|
513
|
+
adding: artifacts.length
|
|
514
|
+
} });
|
|
515
|
+
if (s.bytes + chunkBytes > this.maxBytes) throw new AxiomError("ERR_PLAN_SESSION_STATE", `session would hold ${s.bytes + chunkBytes} bytes; max ${this.maxBytes}`, { details: {
|
|
516
|
+
sessionId,
|
|
517
|
+
have: s.bytes,
|
|
518
|
+
adding: chunkBytes,
|
|
519
|
+
max: this.maxBytes
|
|
520
|
+
} });
|
|
521
|
+
for (const a of artifacts) if (s.paths.has(a.path)) throw new AxiomError("ERR_INVALID_PLAN", "duplicate artifact path across chunks", {
|
|
522
|
+
path: a.path,
|
|
523
|
+
details: { sessionId }
|
|
524
|
+
});
|
|
525
|
+
for (const a of artifacts) {
|
|
526
|
+
s.paths.add(a.path);
|
|
527
|
+
s.artifacts.push(a);
|
|
528
|
+
}
|
|
529
|
+
s.bytes += chunkBytes;
|
|
530
|
+
s.touchedAt = this.now();
|
|
531
|
+
return s;
|
|
532
|
+
}
|
|
533
|
+
/** Mark sealed and return the assembled Plan input; the session is dropped. */
|
|
534
|
+
seal(sessionId) {
|
|
535
|
+
const s = this.get(sessionId);
|
|
536
|
+
if (s.sealed) throw new AxiomError("ERR_PLAN_SESSION_STATE", "plan session is already sealed", { details: { sessionId } });
|
|
537
|
+
s.sealed = true;
|
|
538
|
+
this.sessions.delete(sessionId);
|
|
539
|
+
return {
|
|
540
|
+
session: s,
|
|
541
|
+
plan: {
|
|
542
|
+
apiVersion: "axiom.dev/v2",
|
|
543
|
+
kind: "Plan",
|
|
544
|
+
...s.header,
|
|
545
|
+
artifacts: s.artifacts
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
/** Drop an unsealed session without compiling. Unknown ids are a no-op. */
|
|
550
|
+
abandon(sessionId) {
|
|
551
|
+
return this.sessions.delete(sessionId);
|
|
552
|
+
}
|
|
553
|
+
sweep() {
|
|
554
|
+
const cutoff = this.now() - this.ttlMs;
|
|
555
|
+
for (const [id, s] of this.sessions) if (s.touchedAt < cutoff) this.sessions.delete(id);
|
|
556
|
+
}
|
|
557
|
+
get size() {
|
|
558
|
+
return this.sessions.size;
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
//#endregion
|
|
269
562
|
//#region src/keys.ts
|
|
270
563
|
/**
|
|
271
564
|
* Key material and trust-store I/O for the CLI/MCP layer (D-16).
|
|
@@ -279,6 +572,9 @@ function trustFilePath(root, rel = TRUST_FILE_DEFAULT) {
|
|
|
279
572
|
function trustStatePath(root) {
|
|
280
573
|
return path.join(root, ...TRUST_STATE_FILE.split("/"));
|
|
281
574
|
}
|
|
575
|
+
function trustStateKeyPath(root) {
|
|
576
|
+
return path.join(root, ...TRUST_STATE_KEY_FILE.split("/"));
|
|
577
|
+
}
|
|
282
578
|
async function writeJsonAtomic(file, value, mode) {
|
|
283
579
|
await mkdir(path.dirname(file), { recursive: true });
|
|
284
580
|
const tmp = `${file}.tmp-${process.pid}`;
|
|
@@ -302,7 +598,7 @@ async function verifyBundleAgainstRoot(root, bundle, rel = TRUST_FILE_DEFAULT) {
|
|
|
302
598
|
const store = await loadTrustStore(root, rel);
|
|
303
599
|
if (store === void 0) return void 0;
|
|
304
600
|
const v = verifyBundleSignatures(bundle, store, bundle.manifest.counter);
|
|
305
|
-
|
|
601
|
+
const report = {
|
|
306
602
|
trustFile: rel,
|
|
307
603
|
keyids: v.keyids,
|
|
308
604
|
findings: v.findings.map((f) => ({
|
|
@@ -311,6 +607,8 @@ async function verifyBundleAgainstRoot(root, bundle, rel = TRUST_FILE_DEFAULT) {
|
|
|
311
607
|
})),
|
|
312
608
|
ok: v.keyids.length > 0 && v.findings.length === 0
|
|
313
609
|
};
|
|
610
|
+
if (!report.ok) report.code = (bundle.signatures ?? []).length === 0 ? "ERR_SIGNATURE_MISSING" : "ERR_SIGNATURE_INVALID";
|
|
611
|
+
return report;
|
|
314
612
|
}
|
|
315
613
|
async function loadTrustStore(root, rel = TRUST_FILE_DEFAULT) {
|
|
316
614
|
const raw = await readJsonOrUndefined(trustFilePath(root, rel));
|
|
@@ -319,11 +617,47 @@ async function loadTrustStore(root, rel = TRUST_FILE_DEFAULT) {
|
|
|
319
617
|
if (!parsed.success) throw new AxiomError("ERR_INVALID_PROFILE", `trust store ${rel} is invalid`, { details: { issues: parsed.error.issues.slice(0, 10).map((i) => i.message) } });
|
|
320
618
|
return parsed.data;
|
|
321
619
|
}
|
|
620
|
+
async function loadStateKey(root) {
|
|
621
|
+
let text;
|
|
622
|
+
try {
|
|
623
|
+
text = await readFile(trustStateKeyPath(root), "utf8");
|
|
624
|
+
} catch (err) {
|
|
625
|
+
if (err.code === "ENOENT") return void 0;
|
|
626
|
+
throw err;
|
|
627
|
+
}
|
|
628
|
+
const hex = text.trim();
|
|
629
|
+
if (!/^[0-9a-f]{64}$/.test(hex)) throw new AxiomError("ERR_TRUST_STATE_CORRUPT", `${TRUST_STATE_KEY_FILE} is not 32 hex bytes`);
|
|
630
|
+
return hex;
|
|
631
|
+
}
|
|
632
|
+
/** Create `.axiom/trust/state.key` (32 random bytes, hex, 0600) when absent; return the key. */
|
|
633
|
+
async function ensureStateKey(root) {
|
|
634
|
+
const existing = await loadStateKey(root);
|
|
635
|
+
if (existing !== void 0) return existing;
|
|
636
|
+
const hex = randomBytes(32).toString("hex");
|
|
637
|
+
const file = trustStateKeyPath(root);
|
|
638
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
639
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
640
|
+
await writeFile(tmp, `${hex}\n`, {
|
|
641
|
+
encoding: "utf8",
|
|
642
|
+
mode: 384
|
|
643
|
+
});
|
|
644
|
+
await chmod(tmp, 384).catch(() => void 0);
|
|
645
|
+
await rename(tmp, file);
|
|
646
|
+
return hex;
|
|
647
|
+
}
|
|
322
648
|
async function loadTrustState(root) {
|
|
323
|
-
|
|
649
|
+
let raw;
|
|
650
|
+
try {
|
|
651
|
+
raw = await readJsonOrUndefined(trustStatePath(root));
|
|
652
|
+
} catch (err) {
|
|
653
|
+
if (err instanceof SyntaxError) throw new AxiomError("ERR_TRUST_STATE_CORRUPT", `${TRUST_STATE_FILE} is not valid JSON`, { cause: err });
|
|
654
|
+
throw err;
|
|
655
|
+
}
|
|
324
656
|
if (raw === void 0) return void 0;
|
|
325
657
|
const parsed = TrustStateSchema.safeParse(raw);
|
|
326
|
-
if (!parsed.success) throw new AxiomError("
|
|
658
|
+
if (!parsed.success) throw new AxiomError("ERR_TRUST_STATE_CORRUPT", `${TRUST_STATE_FILE} is invalid`, { details: { issues: parsed.error.issues.slice(0, 5).map((i) => i.message) } });
|
|
659
|
+
const keyHex = await loadStateKey(root);
|
|
660
|
+
if (keyHex !== void 0 && !trustStateMacOk(parsed.data, keyHex)) throw new AxiomError("ERR_TRUST_STATE_CORRUPT", `${TRUST_STATE_FILE} failed its MAC (edited by hand, or ${TRUST_STATE_KEY_FILE} rotated)`, { details: { reason: parsed.data.mac === void 0 ? "NO_MAC" : "BAD_MAC" } });
|
|
327
661
|
return parsed.data;
|
|
328
662
|
}
|
|
329
663
|
/**
|
|
@@ -336,11 +670,16 @@ async function advanceTrustState(root, bundle) {
|
|
|
336
670
|
if (counter === void 0) return void 0;
|
|
337
671
|
const cur = await loadTrustState(root);
|
|
338
672
|
if (cur !== void 0 && cur.lastCounter >= counter) return cur;
|
|
339
|
-
const
|
|
673
|
+
const keyHex = await ensureStateKey(root);
|
|
674
|
+
const unsigned = {
|
|
340
675
|
version: 1,
|
|
341
676
|
lastCounter: counter,
|
|
342
677
|
manifestDigest: bundle.manifestDigest
|
|
343
678
|
};
|
|
679
|
+
const next = {
|
|
680
|
+
...unsigned,
|
|
681
|
+
mac: trustStateMac(unsigned, keyHex)
|
|
682
|
+
};
|
|
344
683
|
await writeJsonAtomic(trustStatePath(root), next);
|
|
345
684
|
return next;
|
|
346
685
|
}
|
|
@@ -616,18 +955,20 @@ async function profileFor(ctx, bundle, name, rootReal) {
|
|
|
616
955
|
});
|
|
617
956
|
return loadProfile(profileName, { searchDirs });
|
|
618
957
|
}
|
|
619
|
-
async function checkBundle(ctx, bundle, profileName, rootReal) {
|
|
958
|
+
async function checkBundle(ctx, bundle, profileName, rootReal, signal) {
|
|
620
959
|
const opts = {
|
|
621
960
|
bundle,
|
|
622
961
|
profile: await profileFor(ctx, bundle, profileName, rootReal),
|
|
623
962
|
checks: bundle.manifest.checks,
|
|
624
963
|
...ctx.guards
|
|
625
964
|
};
|
|
965
|
+
if (signal !== void 0) opts.signal = signal;
|
|
626
966
|
if (rootReal !== void 0) {
|
|
627
967
|
opts.root = rootReal;
|
|
628
968
|
opts.casDir = path.join(rootReal, ".axiom", "cas");
|
|
629
969
|
}
|
|
630
970
|
const report = await runChecks(opts);
|
|
971
|
+
if (signal?.aborted) throw new AxiomError("ERR_TASK_CANCELLED", "check cancelled", { details: { manifestDigest: bundle.manifestDigest } });
|
|
631
972
|
if (rootReal !== void 0) {
|
|
632
973
|
await saveReport(rootReal, report);
|
|
633
974
|
ctx.seenRoots.add(rootReal);
|
|
@@ -674,7 +1015,9 @@ const ManifestVerifyOutput = z.object({
|
|
|
674
1015
|
id: z.string(),
|
|
675
1016
|
message: z.string()
|
|
676
1017
|
})),
|
|
677
|
-
ok: z.boolean()
|
|
1018
|
+
ok: z.boolean(),
|
|
1019
|
+
/** `ERR_SIGNATURE_MISSING` (no signatures at all) or `ERR_SIGNATURE_INVALID`; absent when `ok`. */
|
|
1020
|
+
code: z.enum(["ERR_SIGNATURE_MISSING", "ERR_SIGNATURE_INVALID"]).optional()
|
|
678
1021
|
}).optional()
|
|
679
1022
|
});
|
|
680
1023
|
const RollbackOutput = z.object({
|
|
@@ -715,6 +1058,43 @@ const AxmParseOutput = z.object({
|
|
|
715
1058
|
}))
|
|
716
1059
|
});
|
|
717
1060
|
const BundleOrRef = z.union([DigestRefSchema, LooseObject]).describe("A ManifestBundle object, or `sha256:<hex>` of a bundle stored under <root>/.axiom/manifests");
|
|
1061
|
+
const TaskDescriptorOutput = z.object({
|
|
1062
|
+
taskId: z.string(),
|
|
1063
|
+
tool: z.string(),
|
|
1064
|
+
status: TaskStatusSchema,
|
|
1065
|
+
pollIntervalMs: z.int().positive(),
|
|
1066
|
+
ttlMs: z.int().positive(),
|
|
1067
|
+
elapsedMs: z.int().nonnegative()
|
|
1068
|
+
});
|
|
1069
|
+
/** `axiom_task_get`: the descriptor plus, once terminal, the result or the error. */
|
|
1070
|
+
const TaskGetOutput = TaskDescriptorOutput.extend({
|
|
1071
|
+
/** Present iff `status === "completed"`; for `axiom_check` tasks a `CheckReport`. */
|
|
1072
|
+
result: CheckReportSchema.optional(),
|
|
1073
|
+
/** Present iff `status` is `failed` or `cancelled`. */
|
|
1074
|
+
error: TaskErrorSchema.optional()
|
|
1075
|
+
});
|
|
1076
|
+
const PlanHeaderShape = {
|
|
1077
|
+
name: PlanSchema.shape.name,
|
|
1078
|
+
intent: PlanSchema.shape.intent,
|
|
1079
|
+
profile: PlanSchema.shape.profile.optional(),
|
|
1080
|
+
capabilities: PlanSchema.shape.capabilities.optional(),
|
|
1081
|
+
checks: PlanSchema.shape.checks.optional(),
|
|
1082
|
+
counter: PlanSchema.shape.counter,
|
|
1083
|
+
metadata: PlanSchema.shape.metadata.optional()
|
|
1084
|
+
};
|
|
1085
|
+
const PlanSessionOutput = z.object({
|
|
1086
|
+
sessionId: z.string(),
|
|
1087
|
+
artifacts: z.int().nonnegative(),
|
|
1088
|
+
bytes: z.int().nonnegative(),
|
|
1089
|
+
/** Remaining budget before `axiom_plan_add` refuses with `ERR_PLAN_SESSION_STATE`. */
|
|
1090
|
+
limits: z.object({
|
|
1091
|
+
maxArtifacts: z.int().positive(),
|
|
1092
|
+
maxBytes: z.int().positive()
|
|
1093
|
+
}),
|
|
1094
|
+
ttlMs: z.int().positive()
|
|
1095
|
+
});
|
|
1096
|
+
const SessionIdArg = z.string().min(1).describe("sessionId returned by axiom_plan_begin");
|
|
1097
|
+
const TaskIdArg = z.string().min(1).describe("taskId returned by axiom_check_start");
|
|
718
1098
|
const TOOL_DEFS = [
|
|
719
1099
|
defineTool({
|
|
720
1100
|
name: "axiom_plan_validate",
|
|
@@ -775,31 +1155,9 @@ const TOOL_DEFS = [
|
|
|
775
1155
|
annotations: ACT,
|
|
776
1156
|
async handler(ctx, { plan, store, root }) {
|
|
777
1157
|
guardPayloadSize("plan", plan);
|
|
778
|
-
|
|
779
|
-
const opts = {
|
|
780
|
-
store: store ?? "inline",
|
|
781
|
-
emitters: EMITTERS
|
|
782
|
-
};
|
|
783
|
-
if (rootReal !== void 0) opts.root = rootReal;
|
|
784
|
-
const { bundle } = await compilePlan(plan, opts);
|
|
785
|
-
if (rootReal !== void 0) {
|
|
786
|
-
await saveManifest(rootReal, bundle);
|
|
787
|
-
ctx.seenRoots.add(rootReal);
|
|
788
|
-
}
|
|
789
|
-
ctx.log.info("compiled", {
|
|
790
|
-
manifestDigest: bundle.manifestDigest,
|
|
791
|
-
artifacts: bundle.manifest.artifacts.length
|
|
792
|
-
});
|
|
793
|
-
return bundle;
|
|
1158
|
+
return compileToBundle(ctx, plan, store, root);
|
|
794
1159
|
},
|
|
795
|
-
summarize:
|
|
796
|
-
manifestDigest: b.manifestDigest,
|
|
797
|
-
planDigest: b.manifest.planDigest,
|
|
798
|
-
name: b.manifest.name,
|
|
799
|
-
profile: b.manifest.profile,
|
|
800
|
-
artifacts: b.manifest.artifacts.length,
|
|
801
|
-
blobs: Object.keys(b.blobs).length
|
|
802
|
-
})
|
|
1160
|
+
summarize: summarizeBundle
|
|
803
1161
|
}),
|
|
804
1162
|
defineTool({
|
|
805
1163
|
name: "axiom_manifest_verify",
|
|
@@ -861,6 +1219,135 @@ const TOOL_DEFS = [
|
|
|
861
1219
|
},
|
|
862
1220
|
summarize: summarizeReport
|
|
863
1221
|
}),
|
|
1222
|
+
defineTool({
|
|
1223
|
+
name: "axiom_check_start",
|
|
1224
|
+
title: "Start policy checks as a background task",
|
|
1225
|
+
description: "Same evaluation as axiom_check, but returned immediately as a task so long `guard.external` suites (up to 15 min per guard) outlive the client's per-call timeout. Poll axiom_task_get with the returned taskId every pollIntervalMs until status is completed|failed|cancelled; the CheckReport is in `result`. Finished tasks are pollable for ttlMs, then forgotten. Tasks live in this server process only (D-24 — tool-level tasks, not the io.modelcontextprotocol/tasks wire extension).",
|
|
1226
|
+
inputSchema: {
|
|
1227
|
+
bundle: LooseObject.describe("ManifestBundle"),
|
|
1228
|
+
profile: ProfileArg,
|
|
1229
|
+
root: RootArg
|
|
1230
|
+
},
|
|
1231
|
+
outputSchema: TaskDescriptorOutput,
|
|
1232
|
+
annotations: READ,
|
|
1233
|
+
async handler(ctx, { bundle, profile, root }) {
|
|
1234
|
+
const parsed = parseBundle(bundle);
|
|
1235
|
+
const rootReal = await optionalRoot(ctx, root);
|
|
1236
|
+
const meta = {};
|
|
1237
|
+
if (rootReal !== void 0) meta.root = rootReal;
|
|
1238
|
+
const rec = ctx.tasks.start("axiom_check", (signal) => checkBundle(ctx, parsed, profile, rootReal, signal), meta);
|
|
1239
|
+
ctx.log.info("task started", {
|
|
1240
|
+
taskId: rec.taskId,
|
|
1241
|
+
manifestDigest: parsed.manifestDigest,
|
|
1242
|
+
root: rootReal
|
|
1243
|
+
});
|
|
1244
|
+
return ctx.tasks.describe(rec);
|
|
1245
|
+
},
|
|
1246
|
+
summarize: (o) => o
|
|
1247
|
+
}),
|
|
1248
|
+
defineTool({
|
|
1249
|
+
name: "axiom_task_get",
|
|
1250
|
+
title: "Poll a task",
|
|
1251
|
+
description: "Status of a task created by axiom_check_start. While `working` only the descriptor is returned; once terminal, `result` (completed) or `error` (failed|cancelled) is attached. Unknown or expired taskId → ERR_TASK_NOT_FOUND.",
|
|
1252
|
+
inputSchema: { taskId: TaskIdArg },
|
|
1253
|
+
outputSchema: TaskGetOutput,
|
|
1254
|
+
annotations: READ,
|
|
1255
|
+
async handler(ctx, { taskId }) {
|
|
1256
|
+
const rec = ctx.tasks.get(taskId);
|
|
1257
|
+
const out = ctx.tasks.describe(rec);
|
|
1258
|
+
if (rec.status === "completed") out.result = CheckReportSchema.parse(rec.result);
|
|
1259
|
+
else if (rec.error !== void 0) out.error = rec.error;
|
|
1260
|
+
return out;
|
|
1261
|
+
},
|
|
1262
|
+
summarize: (o) => ({
|
|
1263
|
+
taskId: o.taskId,
|
|
1264
|
+
status: o.status,
|
|
1265
|
+
elapsedMs: o.elapsedMs,
|
|
1266
|
+
pollIntervalMs: o.pollIntervalMs,
|
|
1267
|
+
result: o.result === void 0 ? void 0 : summarizeReport(o.result),
|
|
1268
|
+
error: o.error
|
|
1269
|
+
})
|
|
1270
|
+
}),
|
|
1271
|
+
defineTool({
|
|
1272
|
+
name: "axiom_task_cancel",
|
|
1273
|
+
title: "Cancel a task",
|
|
1274
|
+
description: "Abort a `working` task: every running guard process tree is killed and the task ends `cancelled` with error ERR_TASK_CANCELLED. Idempotent — a terminal task is returned unchanged.",
|
|
1275
|
+
inputSchema: { taskId: TaskIdArg },
|
|
1276
|
+
outputSchema: TaskDescriptorOutput,
|
|
1277
|
+
annotations: ACT,
|
|
1278
|
+
async handler(ctx, { taskId }) {
|
|
1279
|
+
const rec = ctx.tasks.cancel(taskId);
|
|
1280
|
+
ctx.log.info("task cancelled", {
|
|
1281
|
+
taskId,
|
|
1282
|
+
status: rec.status
|
|
1283
|
+
});
|
|
1284
|
+
return ctx.tasks.describe(rec);
|
|
1285
|
+
},
|
|
1286
|
+
summarize: (o) => o
|
|
1287
|
+
}),
|
|
1288
|
+
defineTool({
|
|
1289
|
+
name: "axiom_plan_begin",
|
|
1290
|
+
title: "Open a chunked plan session",
|
|
1291
|
+
description: "Start assembling a Plan whose JSON would exceed the 4 MiB per-call cap: send the header here (everything except `artifacts`), append artifacts in chunks with axiom_plan_add, then axiom_plan_seal compiles the whole. The sealed bundle's manifestDigest is identical to a one-shot axiom_plan_compile of the same Plan. Sessions are in-memory, expire after 30 min idle, and hold at most 2000 artifacts / 64 MiB.",
|
|
1292
|
+
inputSchema: PlanHeaderShape,
|
|
1293
|
+
outputSchema: PlanSessionOutput,
|
|
1294
|
+
annotations: ACT,
|
|
1295
|
+
async handler(ctx, header) {
|
|
1296
|
+
const s = ctx.planSessions.begin(header);
|
|
1297
|
+
ctx.log.debug("plan session opened", {
|
|
1298
|
+
sessionId: s.sessionId,
|
|
1299
|
+
name: header.name
|
|
1300
|
+
});
|
|
1301
|
+
return describeSession(ctx.planSessions, s);
|
|
1302
|
+
},
|
|
1303
|
+
summarize: (o) => o
|
|
1304
|
+
}),
|
|
1305
|
+
defineTool({
|
|
1306
|
+
name: "axiom_plan_add",
|
|
1307
|
+
title: "Append artifacts to a plan session",
|
|
1308
|
+
description: "Add a chunk of Plan artifacts (each ≤ 4 MiB call, inline content ≤ 256 KiB per artifact as in a Plan) to an open session. Paths must be unique across every chunk (ERR_INVALID_PLAN otherwise); a sealed or over-budget session is ERR_PLAN_SESSION_STATE.",
|
|
1309
|
+
inputSchema: {
|
|
1310
|
+
sessionId: SessionIdArg,
|
|
1311
|
+
artifacts: z.array(LooseObject).min(1).describe("Plan artifacts, same shape as Plan.artifacts[]")
|
|
1312
|
+
},
|
|
1313
|
+
outputSchema: PlanSessionOutput,
|
|
1314
|
+
annotations: ACT,
|
|
1315
|
+
async handler(ctx, { sessionId, artifacts }) {
|
|
1316
|
+
guardPayloadSize("artifacts", artifacts);
|
|
1317
|
+
const parsed = z.array(PlanArtifactSchema).safeParse(artifacts);
|
|
1318
|
+
if (!parsed.success) throw new AxiomError("ERR_INVALID_PLAN", "artifacts do not match PlanArtifactSchema", { details: { issues: parsed.error.issues.slice(0, 20).map((i) => ({
|
|
1319
|
+
path: i.path.map(String).join("."),
|
|
1320
|
+
message: i.message
|
|
1321
|
+
})) } });
|
|
1322
|
+
const bytes = Buffer.byteLength(JSON.stringify(artifacts), "utf8");
|
|
1323
|
+
const s = ctx.planSessions.add(sessionId, parsed.data, bytes);
|
|
1324
|
+
return describeSession(ctx.planSessions, s);
|
|
1325
|
+
},
|
|
1326
|
+
summarize: (o) => o
|
|
1327
|
+
}),
|
|
1328
|
+
defineTool({
|
|
1329
|
+
name: "axiom_plan_seal",
|
|
1330
|
+
title: "Seal a plan session and compile it",
|
|
1331
|
+
description: "Assemble the session's header + every added artifact into one Plan and compile it exactly like axiom_plan_compile (same options, same digest). The session is consumed whether or not compilation succeeds. Bundles whose inline blobs exceed 4 MiB need `store: cas` (and therefore a root).",
|
|
1332
|
+
inputSchema: {
|
|
1333
|
+
sessionId: SessionIdArg,
|
|
1334
|
+
store: z.enum(["inline", "cas"]).optional().describe("Blob transport; default inline"),
|
|
1335
|
+
root: RootArg
|
|
1336
|
+
},
|
|
1337
|
+
outputSchema: ManifestBundleSchema,
|
|
1338
|
+
annotations: ACT,
|
|
1339
|
+
async handler(ctx, { sessionId, store, root }) {
|
|
1340
|
+
const { plan, session } = ctx.planSessions.seal(sessionId);
|
|
1341
|
+
const bundle = await compileToBundle(ctx, plan, store, root);
|
|
1342
|
+
ctx.log.info("plan session sealed", {
|
|
1343
|
+
sessionId,
|
|
1344
|
+
artifacts: session.artifacts.length,
|
|
1345
|
+
manifestDigest: bundle.manifestDigest
|
|
1346
|
+
});
|
|
1347
|
+
return bundle;
|
|
1348
|
+
},
|
|
1349
|
+
summarize: summarizeBundle
|
|
1350
|
+
}),
|
|
864
1351
|
defineTool({
|
|
865
1352
|
name: "axiom_apply_dry_run",
|
|
866
1353
|
title: "Dry-run apply (stage + diff, no writes to the tree)",
|
|
@@ -1074,6 +1561,47 @@ async function resolveBundleOrRef(ctx, v, label) {
|
|
|
1074
1561
|
}
|
|
1075
1562
|
return parseBundle(v);
|
|
1076
1563
|
}
|
|
1564
|
+
/** Shared by `axiom_plan_compile` and `axiom_plan_seal` so both produce byte-identical bundles. */
|
|
1565
|
+
async function compileToBundle(ctx, plan, store, root) {
|
|
1566
|
+
const rootReal = root !== void 0 || store === "cas" ? (await resolveRoot(ctx.policy, root)).rootReal : void 0;
|
|
1567
|
+
const opts = {
|
|
1568
|
+
store: store ?? "inline",
|
|
1569
|
+
emitters: EMITTERS
|
|
1570
|
+
};
|
|
1571
|
+
if (rootReal !== void 0) opts.root = rootReal;
|
|
1572
|
+
const { bundle } = await compilePlan(plan, opts);
|
|
1573
|
+
if (rootReal !== void 0) {
|
|
1574
|
+
await saveManifest(rootReal, bundle);
|
|
1575
|
+
ctx.seenRoots.add(rootReal);
|
|
1576
|
+
}
|
|
1577
|
+
ctx.log.info("compiled", {
|
|
1578
|
+
manifestDigest: bundle.manifestDigest,
|
|
1579
|
+
artifacts: bundle.manifest.artifacts.length
|
|
1580
|
+
});
|
|
1581
|
+
return bundle;
|
|
1582
|
+
}
|
|
1583
|
+
function summarizeBundle(b) {
|
|
1584
|
+
return {
|
|
1585
|
+
manifestDigest: b.manifestDigest,
|
|
1586
|
+
planDigest: b.manifest.planDigest,
|
|
1587
|
+
name: b.manifest.name,
|
|
1588
|
+
profile: b.manifest.profile,
|
|
1589
|
+
artifacts: b.manifest.artifacts.length,
|
|
1590
|
+
blobs: Object.keys(b.blobs).length
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
function describeSession(store, s) {
|
|
1594
|
+
return {
|
|
1595
|
+
sessionId: s.sessionId,
|
|
1596
|
+
artifacts: s.artifacts.length,
|
|
1597
|
+
bytes: s.bytes,
|
|
1598
|
+
limits: {
|
|
1599
|
+
maxArtifacts: store.maxArtifacts,
|
|
1600
|
+
maxBytes: store.maxBytes
|
|
1601
|
+
},
|
|
1602
|
+
ttlMs: store.ttlMs
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1077
1605
|
function summarizeReport(r) {
|
|
1078
1606
|
return {
|
|
1079
1607
|
manifestDigest: r.manifestDigest,
|
|
@@ -1178,20 +1706,25 @@ function createServer(policy, opts = {}) {
|
|
|
1178
1706
|
const ctx = {
|
|
1179
1707
|
policy,
|
|
1180
1708
|
log,
|
|
1181
|
-
seenRoots: /* @__PURE__ */ new Set()
|
|
1709
|
+
seenRoots: opts.seenRoots ?? /* @__PURE__ */ new Set(),
|
|
1710
|
+
tasks: opts.tasks ?? new TaskStore(),
|
|
1711
|
+
planSessions: opts.planSessions ?? new PlanSessionStore()
|
|
1182
1712
|
};
|
|
1183
1713
|
if (opts.guards !== void 0) ctx.guards = opts.guards;
|
|
1184
1714
|
const server = new McpServer({
|
|
1185
1715
|
name: SERVER_NAME,
|
|
1186
1716
|
version: SERVER_VERSION
|
|
1187
|
-
}, {
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1717
|
+
}, {
|
|
1718
|
+
capabilities: {
|
|
1719
|
+
tools: { listChanged: false },
|
|
1720
|
+
resources: { listChanged: false }
|
|
1721
|
+
},
|
|
1722
|
+
cacheHints: CACHE_HINTS
|
|
1723
|
+
});
|
|
1191
1724
|
for (const def of opts.tools ?? TOOL_DEFS) server.registerTool(def.name, {
|
|
1192
1725
|
title: def.title,
|
|
1193
1726
|
description: def.description,
|
|
1194
|
-
inputSchema: def.inputSchema,
|
|
1727
|
+
inputSchema: z.object(def.inputSchema),
|
|
1195
1728
|
outputSchema: def.outputSchema,
|
|
1196
1729
|
annotations: {
|
|
1197
1730
|
title: def.title,
|
|
@@ -1268,6 +1801,7 @@ function createServer(policy, opts = {}) {
|
|
|
1268
1801
|
log.info("server created", {
|
|
1269
1802
|
name: SERVER_NAME,
|
|
1270
1803
|
version: SERVER_VERSION,
|
|
1804
|
+
era: opts.era ?? "legacy",
|
|
1271
1805
|
roots: [...policy.roots]
|
|
1272
1806
|
});
|
|
1273
1807
|
return server;
|
|
@@ -1289,6 +1823,6 @@ function renderToolsSpec() {
|
|
|
1289
1823
|
return `${JSON.stringify(buildToolsSpec(), null, 2)}\n`;
|
|
1290
1824
|
}
|
|
1291
1825
|
//#endregion
|
|
1292
|
-
export { BUNDLE_BYTES_MAX, LOG_LEVELS, SCHEMA_KINDS, SERVER_NAME, SERVER_VERSION, SUMMARY_LIST_MAX, TOOL_DEFS, buildToolsSpec, createLogger, createRootsPolicy, createServer, isLogLevel, isSameOrInside, isSchemaKind, jsonSchemaFor, renderToolsSpec, resolveRoot, riskClassOf, toStructuredError, toolByName };
|
|
1826
|
+
export { BUNDLE_BYTES_MAX, LOG_LEVELS, PLAN_SESSION_MAX_ARTIFACTS, PLAN_SESSION_MAX_BYTES, PLAN_SESSION_TTL_MS, PlanSessionStore, SCHEMA_KINDS, SERVER_NAME, SERVER_VERSION, SUMMARY_LIST_MAX, TASK_MAX_WORKING, TASK_POLL_INTERVAL_MS, TASK_STATUSES, TASK_TTL_MS, TOOL_DEFS, TaskStore, buildToolsSpec, createLogger, createRootsPolicy, createServer, isLogLevel, isSameOrInside, isSchemaKind, jsonSchemaFor, renderToolsSpec, resolveRoot, riskClassOf, toStructuredError, toolByName };
|
|
1293
1827
|
|
|
1294
1828
|
//# sourceMappingURL=index.js.map
|