trellis 3.2.3 → 3.2.4

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.
Files changed (194) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +4 -0
  3. package/dist/better-sqlite-backend-CDYOAJDF.js +209 -0
  4. package/dist/boot-middleware-LBJX4N2S.js +9 -0
  5. package/dist/boot-middleware-PATBLD52.js +9 -0
  6. package/dist/bridge-G5DPW6TY.js +2361 -0
  7. package/dist/bridge-JSSPQPRH.js +2389 -0
  8. package/dist/bridge-LNXE4HAW.js +2389 -0
  9. package/dist/bridge-OAAXJWSA.js +2389 -0
  10. package/dist/bridge-SFS63RHC.js +2367 -0
  11. package/dist/bridge-U5TOUBVM.js +2389 -0
  12. package/dist/browser/index.js +12 -12
  13. package/dist/{chunk-KSU2GW22.js → chunk-276W52FK.js} +8 -0
  14. package/dist/{chunk-KFQGP6VL.js → chunk-2ESYSVXG.js} +15 -0
  15. package/dist/{chunk-L5N5PR2S.js → chunk-2WW2X2BR.js} +313 -77
  16. package/dist/chunk-3XCOAP6G.js +48 -0
  17. package/dist/chunk-53PDCTJ3.js +1856 -0
  18. package/dist/{chunk-N6MYZT4O.js → chunk-547FZFKO.js} +1 -1
  19. package/dist/{chunk-7DUMWO5L.js → chunk-7WVOPXJV.js} +3 -3
  20. package/dist/chunk-CMSFD6OV.js +11801 -0
  21. package/dist/{chunk-RSWUFTO2.js → chunk-CPUYCCLT.js} +2 -2
  22. package/dist/{chunk-4HYPLFJD.js → chunk-FA5TFVA5.js} +1 -1
  23. package/dist/{chunk-4XIVMVHC.js → chunk-G3XIHPSQ.js} +1 -1
  24. package/dist/{chunk-JHHEXEDM.js → chunk-GCQF75T4.js} +3 -3
  25. package/dist/{chunk-FKQO5A3H.js → chunk-GHG4H3AS.js} +2 -2
  26. package/dist/{chunk-GLM4HGN4.js → chunk-GRWQPKYK.js} +1 -1
  27. package/dist/{chunk-PZ4XYZ4J.js → chunk-GXA73CNC.js} +33 -8
  28. package/dist/{chunk-6GEZ6DN5.js → chunk-H6VTC5SS.js} +3 -3
  29. package/dist/{chunk-S3XLFZ27.js → chunk-IHHSOBJS.js} +1 -1
  30. package/dist/chunk-IJNC25UD.js +2243 -0
  31. package/dist/{chunk-VDAKEOML.js → chunk-J6OUIION.js} +1 -1
  32. package/dist/chunk-JAVAYMMS.js +6 -0
  33. package/dist/{chunk-S6GBJ2ZB.js → chunk-JCQQFLO5.js} +1 -1
  34. package/dist/{chunk-FWRS4CSC.js → chunk-KQG6ZYXP.js} +3 -3
  35. package/dist/chunk-KXAISKZT.js +119 -0
  36. package/dist/chunk-N3R74ZSR.js +7949 -0
  37. package/dist/{chunk-GU374KWZ.js → chunk-NLFRRGX6.js} +3 -3
  38. package/dist/{chunk-XMQ7G77X.js → chunk-OB4CMMX6.js} +1 -1
  39. package/dist/chunk-OKNASCZY.js +182 -0
  40. package/dist/chunk-ONOPPX5E.js +394 -0
  41. package/dist/chunk-QMSU2RAH.js +98 -0
  42. package/dist/{chunk-TCKXSOBP.js → chunk-RARKFVWU.js} +8 -1
  43. package/dist/{chunk-2AACZRII.js → chunk-SJWVDS5M.js} +751 -162
  44. package/dist/{chunk-SPSPV5BC.js → chunk-SZ3VAB5P.js} +159 -2
  45. package/dist/chunk-V2ASD6RQ.js +147 -0
  46. package/dist/{chunk-KQ4A2D2P.js → chunk-V4YCJDYL.js} +58 -38
  47. package/dist/{chunk-LNUIGRDZ.js → chunk-VWKLKLYB.js} +1 -1
  48. package/dist/chunk-W5PADCDF.js +1856 -0
  49. package/dist/{chunk-MLC5BUYO.js → chunk-WMERMSWI.js} +1 -1
  50. package/dist/chunk-WXAURJUD.js +1948 -0
  51. package/dist/chunk-YC5I32PS.js +147 -0
  52. package/dist/{chunk-VBYUTRXX.js → chunk-YKVB4LUQ.js} +3 -3
  53. package/dist/{chunk-NWSW5YNS.js → chunk-YPT6XMS4.js} +20 -50
  54. package/dist/cli/deploy-cli.d.ts.map +1 -1
  55. package/dist/cli/deploy-cli.js +43 -4
  56. package/dist/cli/gateway-deploy-cli.d.ts +19 -0
  57. package/dist/cli/gateway-deploy-cli.d.ts.map +1 -0
  58. package/dist/cli/index.d.ts.map +1 -1
  59. package/dist/cli/index.js +1360 -881
  60. package/dist/cli/lane.d.ts.map +1 -1
  61. package/dist/cli/protocol.d.ts +6 -0
  62. package/dist/cli/protocol.d.ts.map +1 -0
  63. package/dist/client/index.browser.js +2 -2
  64. package/dist/client/index.js +11 -11
  65. package/dist/client/sdk.browser.js +1 -1
  66. package/dist/client/sdk.js +2 -2
  67. package/dist/cms/index.js +1 -1
  68. package/dist/config-KMY6RRK3.js +19 -0
  69. package/dist/core/index.js +13 -13
  70. package/dist/core/kernel/boot-middleware.d.ts +1 -1
  71. package/dist/core/kernel/boot-middleware.d.ts.map +1 -1
  72. package/dist/core/kernel/schema-middleware.d.ts +2 -1
  73. package/dist/core/kernel/schema-middleware.d.ts.map +1 -1
  74. package/dist/core/kernel/trellis-kernel.d.ts +10 -10
  75. package/dist/core/kernel/trellis-kernel.d.ts.map +1 -1
  76. package/dist/core/persist/factory.js +2 -2
  77. package/dist/core/persist/sqljs-backend.js +2 -2
  78. package/dist/db/index.js +30 -25
  79. package/dist/decisions/index.js +4 -4
  80. package/dist/embeddings/index.js +2 -2
  81. package/dist/engine.d.ts +24 -6
  82. package/dist/engine.d.ts.map +1 -1
  83. package/dist/gateway-deploy-cli-4TU4V3QI.js +312 -0
  84. package/dist/gateway-deploy-cli-A4ISTDHJ.js +298 -0
  85. package/dist/gateway-deploy-cli-D3XTKJQJ.js +312 -0
  86. package/dist/gateway-deploy-cli-ECV3OQCN.js +305 -0
  87. package/dist/gateway-deploy-cli-JUHKVIJE.js +296 -0
  88. package/dist/gateway-deploy-cli-NSDW2JLT.js +310 -0
  89. package/dist/gateway-serve-CM5EAZ52.js +113 -0
  90. package/dist/import-AJLYHFIA.js +10 -0
  91. package/dist/index.js +11 -11
  92. package/dist/launch-explorer-FVX6ZABW.js +95 -0
  93. package/dist/links/index.js +2 -2
  94. package/dist/mcp/bridge.d.ts +45 -0
  95. package/dist/mcp/bridge.d.ts.map +1 -0
  96. package/dist/mcp/discovery.d.ts +15 -0
  97. package/dist/mcp/discovery.d.ts.map +1 -0
  98. package/dist/mcp/gateway-config.d.ts +20 -0
  99. package/dist/mcp/gateway-config.d.ts.map +1 -0
  100. package/dist/mcp/gateway-serve.d.ts +19 -0
  101. package/dist/mcp/gateway-serve.d.ts.map +1 -0
  102. package/dist/mcp/gateway-serve.js +119 -0
  103. package/dist/mcp/graph-summary.d.ts +47 -0
  104. package/dist/mcp/graph-summary.d.ts.map +1 -0
  105. package/dist/mcp/room-audit.d.ts +14 -0
  106. package/dist/mcp/room-audit.d.ts.map +1 -0
  107. package/dist/mcp/room-helpers.d.ts +43 -0
  108. package/dist/mcp/room-helpers.d.ts.map +1 -0
  109. package/dist/mcp/room-registry.d.ts +32 -0
  110. package/dist/mcp/room-registry.d.ts.map +1 -0
  111. package/dist/mcp/room.d.ts +28 -0
  112. package/dist/mcp/room.d.ts.map +1 -0
  113. package/dist/plugins/agent-memory/index.js +1 -1
  114. package/dist/plugins/idea-garden/index.js +1 -1
  115. package/dist/plugins/plan-approval/index.js +1 -1
  116. package/dist/plugins/proactive-watcher/index.js +1 -1
  117. package/dist/protocol/envelope.d.ts +30 -0
  118. package/dist/protocol/envelope.d.ts.map +1 -0
  119. package/dist/protocol/index.d.ts +3 -0
  120. package/dist/protocol/index.d.ts.map +1 -0
  121. package/dist/protocol/whereami.d.ts +33 -0
  122. package/dist/protocol/whereami.d.ts.map +1 -0
  123. package/dist/query-KHDDIR5G.js +31 -0
  124. package/dist/react/index.js +1 -1
  125. package/dist/react/realtime.js +1 -1
  126. package/dist/react/schema-hooks.js +5 -5
  127. package/dist/realtime/index.js +3 -3
  128. package/dist/realtime/relay-server.d.ts.map +1 -1
  129. package/dist/realtime/room.d.ts.map +1 -1
  130. package/dist/realtime.bundle.js +2 -0
  131. package/dist/relay-server-BT6DCJ7F.js +12 -0
  132. package/dist/relay-server-MPQVGNQU.js +12 -0
  133. package/dist/relay-server-PAPW2EWC.js +12 -0
  134. package/dist/remote-manager-6ERQUBSL.js +224 -0
  135. package/dist/remote-manager-G665WWYY.js +224 -0
  136. package/dist/remote-manager-HJTEZ4OF.js +224 -0
  137. package/dist/remote-manager-NYP2Y5UR.js +224 -0
  138. package/dist/remote-manager-WGLCAMMC.js +224 -0
  139. package/dist/scaffold/write.d.ts.map +1 -1
  140. package/dist/schema/index.js +1 -1
  141. package/dist/server/cors.d.ts.map +1 -1
  142. package/dist/server/deploy-gateway.d.ts +27 -0
  143. package/dist/server/deploy-gateway.d.ts.map +1 -0
  144. package/dist/server/deploy.d.ts.map +1 -1
  145. package/dist/server/deploy.js +3 -2
  146. package/dist/server/discovery-mcp-gateway.d.ts +13 -0
  147. package/dist/server/discovery-mcp-gateway.d.ts.map +1 -0
  148. package/dist/server/index.js +38 -31
  149. package/dist/server/mcp-gateway.d.ts +27 -0
  150. package/dist/server/mcp-gateway.d.ts.map +1 -0
  151. package/dist/server/mcp-oauth-metadata.d.ts +31 -0
  152. package/dist/server/mcp-oauth-metadata.d.ts.map +1 -0
  153. package/dist/server/node-adapter.d.ts +7 -0
  154. package/dist/server/node-adapter.d.ts.map +1 -1
  155. package/dist/server/node-adapter.js +2 -2
  156. package/dist/server/realtime.d.ts.map +1 -1
  157. package/dist/server/server.d.ts +8 -0
  158. package/dist/server/server.d.ts.map +1 -1
  159. package/dist/server/streamable-mcp-gateway.d.ts +12 -0
  160. package/dist/server/streamable-mcp-gateway.d.ts.map +1 -0
  161. package/dist/server/usage-meter.d.ts +11 -0
  162. package/dist/server/usage-meter.d.ts.map +1 -1
  163. package/dist/server-4VEZHXXS.js +23 -0
  164. package/dist/{server-OGVWNDNK.js → server-556BMK4C.js} +1 -1
  165. package/dist/server-FQT2VYRE.js +23 -0
  166. package/dist/server-HGZE4ECC.js +23 -0
  167. package/dist/server-J4JKEAMH.js +23 -0
  168. package/dist/server-LXPM5TFR.js +23 -0
  169. package/dist/server-POB4NEUX.js +18 -0
  170. package/dist/server-VJKAPGPG.js +23 -0
  171. package/dist/server-YHS3XIIG.js +23 -0
  172. package/dist/server-ZHTPWPXS.js +23 -0
  173. package/dist/sprites-NC6D7YZS.js +27 -0
  174. package/dist/svelte/index.js +3 -3
  175. package/dist/svelte/schema-hooks.js +4 -4
  176. package/dist/sync/index.js +4 -4
  177. package/dist/tenancy-2SZTP4UD.js +16 -0
  178. package/dist/tenancy-7MTSESMJ.js +16 -0
  179. package/dist/tenancy-U4E7ZEAG.js +16 -0
  180. package/dist/vcs/decompose.d.ts.map +1 -1
  181. package/dist/vcs/index.js +5 -5
  182. package/dist/vcs/lane-disk-materialize.d.ts +11 -0
  183. package/dist/vcs/lane-disk-materialize.d.ts.map +1 -0
  184. package/dist/vcs/lane-worktree.d.ts +21 -0
  185. package/dist/vcs/lane-worktree.d.ts.map +1 -0
  186. package/dist/vcs/store.d.ts +28 -0
  187. package/dist/vcs/store.d.ts.map +1 -0
  188. package/dist/vcs/types.d.ts +5 -0
  189. package/dist/vcs/types.d.ts.map +1 -1
  190. package/dist/vm-config-YZRJ3KUB.js +21 -0
  191. package/dist/vue/index.js +3 -3
  192. package/dist/vue/schema-hooks.js +4 -4
  193. package/package.json +4 -3
  194. package/dist/chunk-GFZCJ4EH.js +0 -108
@@ -1,6 +1,28 @@
1
+ import {
2
+ McpServer,
3
+ StreamableMcpGateway,
4
+ corsEnabledForConfig,
5
+ corsPreflightResponse,
6
+ discoveryMcpGateway,
7
+ mcpServiceDocument,
8
+ oauthAuthorizationServerMetadata,
9
+ oauthProtectedResourceMetadata,
10
+ withCors
11
+ } from "./chunk-53PDCTJ3.js";
12
+ import {
13
+ McpRateLimitError,
14
+ UsageMeter,
15
+ assertMcpBudget,
16
+ collectionSlugFromMetaId,
17
+ resolveCollectionId,
18
+ resolveMcpTenantId,
19
+ resolveUsageTenantId,
20
+ verifyAdminKey,
21
+ writeContext
22
+ } from "./chunk-CMSFD6OV.js";
1
23
  import {
2
24
  DEFAULT_TENANT
3
- } from "./chunk-JHHEXEDM.js";
25
+ } from "./chunk-GCQF75T4.js";
4
26
  import {
5
27
  rel
6
28
  } from "./chunk-44FFQKQX.js";
@@ -14,8 +36,8 @@ import {
14
36
  } from "./chunk-X5FLTRUB.js";
15
37
 
16
38
  // src/server/server.ts
17
- import { existsSync as existsSync2, readFileSync } from "fs";
18
- import { dirname, join as join2 } from "path";
39
+ import { existsSync, readFileSync } from "fs";
40
+ import { dirname, join } from "path";
19
41
  import { fileURLToPath } from "url";
20
42
 
21
43
  // src/server/auth.ts
@@ -370,114 +392,6 @@ async function hydrateAndResolve(kernel, bindings, entityType, resolve) {
370
392
  });
371
393
  }
372
394
 
373
- // src/server/usage-meter.ts
374
- import { existsSync, readdirSync, statSync } from "fs";
375
- import { join } from "path";
376
- var EMPTY_BUCKET = () => ({
377
- graph_io: 0,
378
- storage_bytes: 0,
379
- egress_bytes: 0
380
- });
381
- function resolveUsageTenantId(tenantId) {
382
- return tenantId ?? DEFAULT_TENANT;
383
- }
384
- function dayKey(date) {
385
- return date.toISOString().slice(0, 10);
386
- }
387
- function dirSize(dir) {
388
- let total = 0;
389
- for (const ent of readdirSync(dir, { withFileTypes: true })) {
390
- const p = join(dir, ent.name);
391
- if (ent.isDirectory()) total += dirSize(p);
392
- else if (ent.isFile()) total += statSync(p).size;
393
- }
394
- return total;
395
- }
396
- function sampleTenantStorage(pool, tenantId) {
397
- let total = 0;
398
- const sqlitePath = pool.dbFilePath(tenantId);
399
- if (existsSync(sqlitePath)) {
400
- total += statSync(sqlitePath).size;
401
- }
402
- const blobDir = join(pool.dataPath(), "blobs");
403
- if (existsSync(blobDir)) {
404
- total += dirSize(blobDir);
405
- }
406
- return total;
407
- }
408
- function verifyAdminKey(req) {
409
- const expected = process.env.TURTLEDB_ADMIN_KEY;
410
- if (!expected) return false;
411
- const auth = req.headers.get("authorization");
412
- if (auth?.startsWith("Bearer ")) {
413
- return auth.slice("Bearer ".length) === expected;
414
- }
415
- return req.headers.get("x-turtledb-admin-key") === expected;
416
- }
417
- var UsageMeter = class {
418
- buckets = /* @__PURE__ */ new Map();
419
- storageSampledAt = /* @__PURE__ */ new Map();
420
- now;
421
- constructor(opts = {}) {
422
- this.now = opts.now ?? (() => /* @__PURE__ */ new Date());
423
- }
424
- recordGraphIo(tenantId, count = 1) {
425
- if (count <= 0) return;
426
- const bucket = this._bucket(tenantId);
427
- bucket.graph_io += count;
428
- }
429
- recordEgress(tenantId, bytes) {
430
- if (bytes <= 0) return;
431
- const bucket = this._bucket(tenantId);
432
- bucket.egress_bytes += bytes;
433
- }
434
- /** Set storage gauge for the current day (from sampler, not incremental). */
435
- recordStorage(tenantId, bytes) {
436
- const bucket = this._bucket(tenantId);
437
- bucket.storage_bytes = bytes;
438
- this.storageSampledAt.set(`${tenantId}:${this._today()}`, this.now().toISOString());
439
- }
440
- sampleStorage(pool, tenantId) {
441
- const id = resolveUsageTenantId(tenantId);
442
- const bytes = sampleTenantStorage(pool, id);
443
- this.recordStorage(id, bytes);
444
- return bytes;
445
- }
446
- getUsage(tenantId, day) {
447
- const d = day ?? this._today();
448
- const tenantBuckets = this.buckets.get(tenantId);
449
- const meters = tenantBuckets?.get(d) ?? EMPTY_BUCKET();
450
- const sampledAt = this.storageSampledAt.get(`${tenantId}:${d}`);
451
- return {
452
- tenantId,
453
- day: d,
454
- meters: { ...meters },
455
- ...sampledAt ? { storageSampledAt: sampledAt } : {}
456
- };
457
- }
458
- /** All day keys recorded for a tenant (sorted ascending). */
459
- listDays(tenantId) {
460
- const tenantBuckets = this.buckets.get(tenantId);
461
- if (!tenantBuckets) return [];
462
- return Array.from(tenantBuckets.keys()).sort();
463
- }
464
- _today() {
465
- return dayKey(this.now());
466
- }
467
- _bucket(tenantId) {
468
- const id = resolveUsageTenantId(tenantId);
469
- const d = this._today();
470
- if (!this.buckets.has(id)) {
471
- this.buckets.set(id, /* @__PURE__ */ new Map());
472
- }
473
- const tenantBuckets = this.buckets.get(id);
474
- if (!tenantBuckets.has(d)) {
475
- tenantBuckets.set(d, EMPTY_BUCKET());
476
- }
477
- return tenantBuckets.get(d);
478
- }
479
- };
480
-
481
395
  // src/server/realtime.ts
482
396
  var SubscriptionManager = class {
483
397
  clients = /* @__PURE__ */ new Map();
@@ -549,7 +463,6 @@ var SubscriptionManager = class {
549
463
  dead.push(clientId);
550
464
  continue;
551
465
  }
552
- if (client.tenantId !== tid) continue;
553
466
  for (const [subId, sub] of client.subscriptions) {
554
467
  if (sub.tenantId !== tid) continue;
555
468
  await this._pushUpdate(client, sub);
@@ -606,6 +519,9 @@ var SubscriptionManager = class {
606
519
  resolve: opts.resolve
607
520
  };
608
521
  client.subscriptions.set(subId, sub);
522
+ if (tid !== null && client.tenantId !== tid) {
523
+ client.tenantId = tid;
524
+ }
609
525
  this._send(client, { type: "subscribed", id: subId });
610
526
  this._send(client, {
611
527
  type: "data",
@@ -682,55 +598,641 @@ function computeDiff(prev, next) {
682
598
  return { added, updated, removed };
683
599
  }
684
600
 
685
- // src/server/cors.ts
686
- var DEFAULT_METHODS = "GET, POST, PUT, DELETE, OPTIONS";
687
- var DEFAULT_HEADERS = "Content-Type, Authorization";
688
- function corsEnabledForConfig(apiKey) {
689
- return Boolean(process.env.TRELLIS_CORS_ORIGINS) || Boolean(apiKey);
601
+ // src/mcp/room.ts
602
+ import { z as z2 } from "zod";
603
+
604
+ // src/mcp/graph-summary.ts
605
+ var SKIP_ATTRS = /* @__PURE__ */ new Set(["@id", "@type", "id", "type"]);
606
+ function buildRoomGraphSummary(kernel, tenantId, opts = {}) {
607
+ const limit = opts.limit ?? 10;
608
+ const store = kernel.getStore();
609
+ let factCount = 0;
610
+ for (const _ of store.getAllFacts()) factCount++;
611
+ let linkCount = 0;
612
+ for (const _ of store.getAllLinks()) linkCount++;
613
+ const typeCounts = {};
614
+ const entityIds = /* @__PURE__ */ new Set();
615
+ for (const fact of store.getAllFacts()) {
616
+ if (fact.a === "type") {
617
+ const type = String(fact.v);
618
+ typeCounts[type] = (typeCounts[type] ?? 0) + 1;
619
+ entityIds.add(fact.e);
620
+ }
621
+ }
622
+ const entityTypes = Object.entries(typeCounts).sort((a, b) => b[1] - a[1]).slice(0, limit).map(([type, count]) => ({ type, count }));
623
+ const systemOntologies = [];
624
+ const userOntologies = [];
625
+ for (const schema of kernel.listOntologies()) {
626
+ const shortId = schema["@id"].replace(/^(trellis:schema\/|core:)/, "");
627
+ if (schema.tier === "user") {
628
+ userOntologies.push(shortId);
629
+ } else if (schema.tier !== "core") {
630
+ systemOntologies.push(shortId);
631
+ }
632
+ }
633
+ const topAttributes = store.getCatalog().filter((c) => !SKIP_ATTRS.has(c.attribute)).sort((a, b) => b.distinctCount - a.distinctCount).slice(0, limit).map((c) => ({
634
+ attribute: c.attribute,
635
+ distinctCount: c.distinctCount,
636
+ cardinality: c.cardinality
637
+ }));
638
+ const relations = /* @__PURE__ */ new Set();
639
+ for (const link of store.getAllLinks()) relations.add(link.a);
640
+ const recentMutations = kernel.readAllOps().slice(-limit).reverse().map((op) => ({
641
+ kind: op.kind,
642
+ agentId: op.agentId,
643
+ timestamp: op.timestamp,
644
+ entityId: inferEntityIdFromOp(op)
645
+ }));
646
+ return {
647
+ health: {
648
+ status: "ok",
649
+ factCount,
650
+ linkCount,
651
+ entityCount: entityIds.size,
652
+ ops: kernel.readAllOps().length,
653
+ tenantId
654
+ },
655
+ entityTypes,
656
+ ontologies: {
657
+ total: kernel.listOntologies().length,
658
+ system: systemOntologies.slice(0, limit),
659
+ user: userOntologies.slice(0, limit)
660
+ },
661
+ topAttributes,
662
+ links: { total: linkCount, relations: [...relations].sort().slice(0, limit) },
663
+ recentMutations
664
+ };
690
665
  }
691
- function allowedOrigins() {
692
- const raw = process.env.TRELLIS_CORS_ORIGINS?.trim();
693
- if (!raw || raw === "*") return "*";
694
- return raw.split(",").map((s) => s.trim()).filter(Boolean);
666
+ function inferEntityIdFromOp(op) {
667
+ const facts = op.facts ?? op.deleteFacts;
668
+ if (!facts?.length) return void 0;
669
+ const typeFact = facts.find((f) => "a" in f && f.a === "type");
670
+ return typeFact?.e ?? facts[0]?.e;
695
671
  }
696
- function corsHeaders(req) {
697
- const origins = allowedOrigins();
698
- const requestOrigin = req.headers.get("Origin");
699
- let allowOrigin = "*";
700
- if (origins !== "*") {
701
- if (requestOrigin && origins.includes(requestOrigin)) {
702
- allowOrigin = requestOrigin;
703
- } else if (origins.length === 1) {
704
- allowOrigin = origins[0];
672
+
673
+ // src/mcp/room-audit.ts
674
+ function sanitizeInput(params) {
675
+ const out = {};
676
+ for (const [key, value] of Object.entries(params)) {
677
+ if (typeof value === "string" && value.length > 2e3) {
678
+ out[key] = value.slice(0, 2e3) + "\u2026";
705
679
  } else {
706
- allowOrigin = origins[0] ?? "*";
680
+ out[key] = value;
707
681
  }
708
682
  }
709
- return {
710
- "Access-Control-Allow-Origin": allowOrigin,
711
- "Access-Control-Allow-Methods": DEFAULT_METHODS,
712
- "Access-Control-Allow-Headers": DEFAULT_HEADERS,
713
- Vary: "Origin"
714
- };
683
+ return out;
684
+ }
685
+ function summarizeText(text2, max = 500) {
686
+ return text2.length > max ? text2.slice(0, max) + "\u2026" : text2;
715
687
  }
716
- function withCors(req, res) {
717
- const headers = new Headers(res.headers);
718
- for (const [key, value] of Object.entries(corsHeaders(req))) {
719
- headers.set(key, value);
720
- }
721
- return new Response(res.body, {
722
- status: res.status,
723
- statusText: res.statusText,
724
- headers
688
+ async function recordRoomMcpAudit(kernel, agentId, toolName, params, resultText, wctx, relatedEntities = []) {
689
+ try {
690
+ const id = `decision:mcp-${crypto.randomUUID().slice(0, 8)}`;
691
+ const links = relatedEntities.map((targetEntityId) => ({
692
+ attribute: "relatedTo",
693
+ targetEntityId
694
+ }));
695
+ await kernel.createEntity(
696
+ id,
697
+ "Decision",
698
+ {
699
+ title: `MCP ${toolName}`,
700
+ toolName,
701
+ input: JSON.stringify(sanitizeInput(params)),
702
+ outputSummary: summarizeText(resultText),
703
+ createdBy: agentId,
704
+ source: "room-mcp"
705
+ },
706
+ links.length > 0 ? links : void 0,
707
+ wctx
708
+ );
709
+ return id;
710
+ } catch {
711
+ return null;
712
+ }
713
+ }
714
+ function scheduleRoomMcpAudit(kernelPromise, agentId, toolName, params, resultText, wctx, relatedEntities) {
715
+ void kernelPromise.then(
716
+ (kernel) => recordRoomMcpAudit(
717
+ kernel,
718
+ agentId,
719
+ toolName,
720
+ params,
721
+ resultText,
722
+ wctx,
723
+ relatedEntities
724
+ )
725
+ );
726
+ }
727
+
728
+ // src/mcp/room.ts
729
+ var laneSchema = z2.string().optional().describe(
730
+ "Draft lane for op attribution (e.g. agent:cursor). Defaults to authenticated user or agent:room-mcp."
731
+ );
732
+ var tenantIdSchema = z2.string().optional().describe(
733
+ "Tenant id (e.g. embed-design-review). Overrides session default. Use for Playground multi-tenant rooms."
734
+ );
735
+ var roomSchema = z2.string().optional().describe(
736
+ "Playground ?room= slug \u2014 resolves to tenant embed-{slug} (same as playground.trellis.computer session rooms)."
737
+ );
738
+ function resolveToolTenant(ctx, args) {
739
+ return resolveMcpTenantId({
740
+ defaultTenantId: ctx.tenantId,
741
+ headerTenant: ctx.headerTenant,
742
+ toolTenantId: args.tenantId,
743
+ roomSlug: args.room
725
744
  });
726
745
  }
727
- function corsPreflightResponse(req) {
728
- return new Response(null, {
729
- status: 204,
730
- headers: corsHeaders(req)
746
+ function text(content) {
747
+ return { content: [{ type: "text", text: content }] };
748
+ }
749
+ function jsonText(data) {
750
+ return text(JSON.stringify(data, null, 2));
751
+ }
752
+ function toolError(err) {
753
+ if (err instanceof PermissionError || err instanceof McpRateLimitError) {
754
+ return text(err.message);
755
+ }
756
+ return text(err instanceof Error ? err.message : String(err));
757
+ }
758
+ async function withMcpIo(ctx, tenantId, fn) {
759
+ assertMcpBudget(ctx.meter, tenantId);
760
+ const result = await fn();
761
+ ctx.meter?.recordGraphIo(resolveUsageTenantId(tenantId));
762
+ return result;
763
+ }
764
+ function auditWrite(kernel, wctx, toolName, params, payload, relatedEntities = []) {
765
+ const agentId = wctx.agentId ?? "agent:room-mcp";
766
+ const resultText = typeof payload === "string" ? payload : JSON.stringify(payload);
767
+ scheduleRoomMcpAudit(
768
+ Promise.resolve(kernel),
769
+ agentId,
770
+ toolName,
771
+ params,
772
+ resultText,
773
+ wctx,
774
+ relatedEntities
775
+ );
776
+ }
777
+ function createRoomMcpServer(ctx) {
778
+ const server = new McpServer({
779
+ name: "trellis-room",
780
+ version: "0.2.0"
731
781
  });
782
+ server.registerTool(
783
+ "get_graph_summary",
784
+ {
785
+ description: "Compact graph overview \u2014 health, entity types, ontologies, top attributes, links, recent ops. Call first.",
786
+ inputSchema: {
787
+ limit: z2.number().optional().describe("Max items per section (default: 10)"),
788
+ tenantId: tenantIdSchema,
789
+ room: roomSchema
790
+ }
791
+ },
792
+ async ({ limit, tenantId, room }) => {
793
+ try {
794
+ const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
795
+ return await withMcpIo(ctx, effectiveTenant, async () => {
796
+ const kernel = await ctx.pool.preload(effectiveTenant);
797
+ return jsonText(
798
+ buildRoomGraphSummary(kernel, effectiveTenant, { limit })
799
+ );
800
+ });
801
+ } catch (err) {
802
+ return toolError(err);
803
+ }
804
+ }
805
+ );
806
+ server.registerTool(
807
+ "graph_health",
808
+ {
809
+ description: "Quick liveness check for the Trellis room graph (ops + entity counts).",
810
+ inputSchema: {
811
+ tenantId: tenantIdSchema,
812
+ room: roomSchema
813
+ }
814
+ },
815
+ async ({ tenantId, room }) => {
816
+ try {
817
+ const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
818
+ return await withMcpIo(ctx, effectiveTenant, async () => {
819
+ const kernel = await ctx.pool.preload(effectiveTenant);
820
+ const entities = kernel.listEntities();
821
+ const byType = {};
822
+ for (const e of entities) {
823
+ byType[e.type] = (byType[e.type] ?? 0) + 1;
824
+ }
825
+ return jsonText({
826
+ status: "ok",
827
+ ops: kernel.readAllOps().length,
828
+ entities: entities.length,
829
+ byType,
830
+ tenantId: effectiveTenant
831
+ });
832
+ });
833
+ } catch (err) {
834
+ return toolError(err);
835
+ }
836
+ }
837
+ );
838
+ server.registerTool(
839
+ "query_graph",
840
+ {
841
+ description: "Run an EQL-S query against the room graph.",
842
+ inputSchema: {
843
+ query: z2.string().describe("EQL-S query string"),
844
+ tenantId: tenantIdSchema,
845
+ room: roomSchema
846
+ }
847
+ },
848
+ async ({ query, tenantId, room }) => {
849
+ try {
850
+ const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
851
+ return await withMcpIo(ctx, effectiveTenant, async () => {
852
+ const parsed = parseSimple(query);
853
+ const kernel = await ctx.pool.preload(effectiveTenant);
854
+ const result = await kernel.query(parsed);
855
+ const bindings = hydrateBindings(
856
+ kernel,
857
+ result.bindings
858
+ );
859
+ return jsonText({
860
+ bindings,
861
+ executionTime: result.executionTime
862
+ });
863
+ });
864
+ } catch (err) {
865
+ return toolError(err);
866
+ }
867
+ }
868
+ );
869
+ server.registerTool(
870
+ "get_node",
871
+ {
872
+ description: "Read a single entity by ID.",
873
+ inputSchema: {
874
+ id: z2.string().describe("Entity ID"),
875
+ tenantId: tenantIdSchema,
876
+ room: roomSchema
877
+ }
878
+ },
879
+ async ({ id, tenantId, room }) => {
880
+ try {
881
+ const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
882
+ return await withMcpIo(ctx, effectiveTenant, async () => {
883
+ const kernel = await ctx.pool.preload(effectiveTenant);
884
+ const entity = kernel.getEntity(id);
885
+ if (!entity) return text(`Not found: ${id}`);
886
+ ctx.permissions?.assert(ctx.auth, entity.type, "read", entity);
887
+ return jsonText(entityRecordToPlain(entity));
888
+ });
889
+ } catch (err) {
890
+ return toolError(err);
891
+ }
892
+ }
893
+ );
894
+ server.registerTool(
895
+ "create_node",
896
+ {
897
+ description: "Create a new entity in the room graph. Optionally pass links for relations.",
898
+ inputSchema: {
899
+ type: z2.string().describe("Entity type name"),
900
+ id: z2.string().optional().describe("Optional explicit entity ID"),
901
+ attributes: z2.record(z2.string(), z2.unknown()).optional().describe("Entity attributes"),
902
+ links: z2.array(
903
+ z2.object({
904
+ attribute: z2.string(),
905
+ targetEntityId: z2.string()
906
+ })
907
+ ).optional().describe("Relation links to create with the entity"),
908
+ lane: laneSchema,
909
+ tenantId: tenantIdSchema,
910
+ room: roomSchema
911
+ }
912
+ },
913
+ async ({ type, id, attributes, links, lane, tenantId, room }) => {
914
+ try {
915
+ const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
916
+ ctx.permissions?.assert(ctx.auth, type, "create");
917
+ const wctx = writeContext(lane, ctx.auth, ctx.headerLane);
918
+ return await withMcpIo(ctx, effectiveTenant, async () => {
919
+ const kernel = await ctx.pool.preload(effectiveTenant);
920
+ const entityId2 = id ?? `${type.toLowerCase()}:${crypto.randomUUID()}`;
921
+ const attrs = { ...attributes ?? {} };
922
+ if (ctx.auth.userId) attrs.createdBy = ctx.auth.userId;
923
+ if (effectiveTenant) attrs.tenantId = effectiveTenant;
924
+ attrs.laneId = wctx.agentId;
925
+ const result = await kernel.createEntity(
926
+ entityId2,
927
+ type,
928
+ attrs,
929
+ links,
930
+ wctx
931
+ );
932
+ await ctx.subs.notify(effectiveTenant);
933
+ const payload = {
934
+ id: entityId2,
935
+ op: result.op.hash,
936
+ lane: wctx.agentId,
937
+ tenantId: effectiveTenant
938
+ };
939
+ auditWrite(
940
+ kernel,
941
+ wctx,
942
+ "create_node",
943
+ { type, id, attributes, links, lane, tenantId, room },
944
+ payload,
945
+ [entityId2]
946
+ );
947
+ return jsonText(payload);
948
+ });
949
+ } catch (err) {
950
+ return toolError(err);
951
+ }
952
+ }
953
+ );
954
+ server.registerTool(
955
+ "update_node",
956
+ {
957
+ description: "Partial update of an entity attributes.",
958
+ inputSchema: {
959
+ id: z2.string().describe("Entity ID"),
960
+ attributes: z2.record(z2.string(), z2.unknown()).describe("Attributes to merge"),
961
+ lane: laneSchema,
962
+ tenantId: tenantIdSchema,
963
+ room: roomSchema
964
+ }
965
+ },
966
+ async ({ id, attributes, lane, tenantId, room }) => {
967
+ try {
968
+ const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
969
+ const wctx = writeContext(lane, ctx.auth, ctx.headerLane);
970
+ return await withMcpIo(ctx, effectiveTenant, async () => {
971
+ const kernel = await ctx.pool.preload(effectiveTenant);
972
+ const entity = kernel.getEntity(id);
973
+ if (!entity) return text(`Not found: ${id}`);
974
+ ctx.permissions?.assert(ctx.auth, entity.type, "update", entity);
975
+ await kernel.updateEntity(id, attributes, wctx);
976
+ await ctx.subs.notify(effectiveTenant);
977
+ const payload = {
978
+ id,
979
+ updated: true,
980
+ lane: wctx.agentId,
981
+ tenantId: effectiveTenant
982
+ };
983
+ auditWrite(
984
+ kernel,
985
+ wctx,
986
+ "update_node",
987
+ { id, attributes, lane, tenantId, room },
988
+ payload,
989
+ [id]
990
+ );
991
+ return jsonText(payload);
992
+ });
993
+ } catch (err) {
994
+ return toolError(err);
995
+ }
996
+ }
997
+ );
998
+ server.registerTool(
999
+ "delete_node",
1000
+ {
1001
+ description: "Delete an entity by ID.",
1002
+ inputSchema: {
1003
+ id: z2.string().describe("Entity ID"),
1004
+ lane: laneSchema,
1005
+ tenantId: tenantIdSchema,
1006
+ room: roomSchema
1007
+ }
1008
+ },
1009
+ async ({ id, lane, tenantId, room }) => {
1010
+ try {
1011
+ const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
1012
+ const wctx = writeContext(lane, ctx.auth, ctx.headerLane);
1013
+ return await withMcpIo(ctx, effectiveTenant, async () => {
1014
+ const kernel = await ctx.pool.preload(effectiveTenant);
1015
+ const entity = kernel.getEntity(id);
1016
+ if (!entity) return text(`Not found: ${id}`);
1017
+ ctx.permissions?.assert(ctx.auth, entity.type, "delete", entity);
1018
+ await kernel.deleteEntity(id, wctx);
1019
+ await ctx.subs.notify(effectiveTenant);
1020
+ const payload = {
1021
+ id,
1022
+ deleted: true,
1023
+ lane: wctx.agentId,
1024
+ tenantId: effectiveTenant
1025
+ };
1026
+ auditWrite(
1027
+ kernel,
1028
+ wctx,
1029
+ "delete_node",
1030
+ { id, lane, tenantId, room },
1031
+ payload,
1032
+ [id]
1033
+ );
1034
+ return jsonText(payload);
1035
+ });
1036
+ } catch (err) {
1037
+ return toolError(err);
1038
+ }
1039
+ }
1040
+ );
1041
+ server.registerTool(
1042
+ "create_collection_record",
1043
+ {
1044
+ description: "Create a Playground CollectionRecord row (shows in Collections UI). Sets collectionId to collectionMeta:<slug> so recordBelongsToCollection matches. Optionally ensureCollection to create CollectionMeta when the collection is missing.",
1045
+ inputSchema: {
1046
+ collectionSlug: z2.string().optional().describe("Collection slug (e.g. people, ideas). Required unless collectionId is set."),
1047
+ collectionId: z2.string().optional().describe("Explicit collection id (e.g. collectionMeta:people). Overrides slug."),
1048
+ title: z2.string().min(1).describe("Record title shown in the collection"),
1049
+ body: z2.string().optional().describe("Optional record body / notes"),
1050
+ sortOrder: z2.number().int().optional().describe("Row sort order within the collection"),
1051
+ attributes: z2.record(z2.string(), z2.unknown()).optional().describe("Extra typed fields (status, tags, etc.) merged into the record"),
1052
+ ensureCollection: z2.boolean().optional().describe(
1053
+ "When true, create CollectionMeta with stable id collectionMeta:<slug> if missing."
1054
+ ),
1055
+ collectionTitle: z2.string().optional().describe("Title for CollectionMeta when ensureCollection creates it"),
1056
+ lane: laneSchema,
1057
+ tenantId: tenantIdSchema,
1058
+ room: roomSchema
1059
+ }
1060
+ },
1061
+ async ({
1062
+ collectionSlug,
1063
+ collectionId,
1064
+ title,
1065
+ body,
1066
+ sortOrder,
1067
+ attributes,
1068
+ ensureCollection,
1069
+ collectionTitle,
1070
+ lane,
1071
+ tenantId,
1072
+ room
1073
+ }) => {
1074
+ try {
1075
+ const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
1076
+ ctx.permissions?.assert(ctx.auth, "CollectionRecord", "create");
1077
+ const wctx = writeContext(lane, ctx.auth, ctx.headerLane);
1078
+ const resolvedCollectionId = resolveCollectionId({
1079
+ collectionSlug,
1080
+ collectionId
1081
+ });
1082
+ const slug = collectionSlug?.trim() || collectionSlugFromMetaId(resolvedCollectionId) || resolvedCollectionId;
1083
+ return await withMcpIo(ctx, effectiveTenant, async () => {
1084
+ const kernel = await ctx.pool.preload(effectiveTenant);
1085
+ let collectionCreated = false;
1086
+ if (ensureCollection) {
1087
+ const existingMeta = kernel.getEntity(resolvedCollectionId);
1088
+ if (!existingMeta) {
1089
+ ctx.permissions?.assert(ctx.auth, "CollectionMeta", "create");
1090
+ const metaAttrs = {
1091
+ title: collectionTitle?.trim() || slug,
1092
+ slug,
1093
+ sortOrder: 0
1094
+ };
1095
+ if (ctx.auth.userId) metaAttrs.createdBy = ctx.auth.userId;
1096
+ if (effectiveTenant) metaAttrs.tenantId = effectiveTenant;
1097
+ metaAttrs.laneId = wctx.agentId;
1098
+ await kernel.createEntity(
1099
+ resolvedCollectionId,
1100
+ "CollectionMeta",
1101
+ metaAttrs,
1102
+ void 0,
1103
+ wctx
1104
+ );
1105
+ collectionCreated = true;
1106
+ }
1107
+ }
1108
+ const recordId = `collectionRecord:${crypto.randomUUID()}`;
1109
+ const recordAttrs = {
1110
+ ...attributes ?? {},
1111
+ collectionId: resolvedCollectionId,
1112
+ title
1113
+ };
1114
+ if (body !== void 0) recordAttrs.body = body;
1115
+ if (sortOrder !== void 0) recordAttrs.sortOrder = sortOrder;
1116
+ if (ctx.auth.userId) recordAttrs.createdBy = ctx.auth.userId;
1117
+ if (effectiveTenant) recordAttrs.tenantId = effectiveTenant;
1118
+ recordAttrs.laneId = wctx.agentId;
1119
+ const result = await kernel.createEntity(
1120
+ recordId,
1121
+ "CollectionRecord",
1122
+ recordAttrs,
1123
+ void 0,
1124
+ wctx
1125
+ );
1126
+ await ctx.subs.notify(effectiveTenant);
1127
+ const payload = {
1128
+ id: recordId,
1129
+ collectionId: resolvedCollectionId,
1130
+ collectionCreated,
1131
+ op: result.op.hash,
1132
+ lane: wctx.agentId,
1133
+ tenantId: effectiveTenant
1134
+ };
1135
+ auditWrite(
1136
+ kernel,
1137
+ wctx,
1138
+ "create_collection_record",
1139
+ {
1140
+ collectionSlug,
1141
+ collectionId,
1142
+ title,
1143
+ body,
1144
+ sortOrder,
1145
+ attributes,
1146
+ ensureCollection,
1147
+ collectionTitle,
1148
+ lane,
1149
+ tenantId,
1150
+ room
1151
+ },
1152
+ payload,
1153
+ collectionCreated ? [recordId, resolvedCollectionId] : [recordId]
1154
+ );
1155
+ return jsonText(payload);
1156
+ });
1157
+ } catch (err) {
1158
+ return toolError(err);
1159
+ }
1160
+ }
1161
+ );
1162
+ server.registerTool(
1163
+ "link_nodes",
1164
+ {
1165
+ description: "Create a semantic link between two entities (assignedTo, belongsTo, references, dependsOn, \u2026).",
1166
+ inputSchema: {
1167
+ e1: z2.string().describe("Source entity ID"),
1168
+ relation: z2.string().describe("Relation attribute name"),
1169
+ e2: z2.string().describe("Target entity ID"),
1170
+ lane: laneSchema,
1171
+ tenantId: tenantIdSchema,
1172
+ room: roomSchema
1173
+ }
1174
+ },
1175
+ async ({ e1, relation, e2, lane, tenantId, room }) => {
1176
+ try {
1177
+ const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
1178
+ const wctx = writeContext(lane, ctx.auth, ctx.headerLane);
1179
+ return await withMcpIo(ctx, effectiveTenant, async () => {
1180
+ const kernel = await ctx.pool.preload(effectiveTenant);
1181
+ const source = kernel.getEntity(e1);
1182
+ const target = kernel.getEntity(e2);
1183
+ if (!source) return text(`Not found: ${e1}`);
1184
+ if (!target) return text(`Not found: ${e2}`);
1185
+ ctx.permissions?.assert(ctx.auth, source.type, "update", source);
1186
+ const result = await kernel.addLink(e1, relation, e2, wctx);
1187
+ await ctx.subs.notify(effectiveTenant);
1188
+ const payload = {
1189
+ e1,
1190
+ relation,
1191
+ e2,
1192
+ op: result.op.hash,
1193
+ lane: wctx.agentId,
1194
+ tenantId: effectiveTenant
1195
+ };
1196
+ auditWrite(
1197
+ kernel,
1198
+ wctx,
1199
+ "link_nodes",
1200
+ { e1, relation, e2, lane, tenantId, room },
1201
+ payload,
1202
+ [e1, e2]
1203
+ );
1204
+ return jsonText(payload);
1205
+ });
1206
+ } catch (err) {
1207
+ return toolError(err);
1208
+ }
1209
+ }
1210
+ );
1211
+ return server;
732
1212
  }
733
1213
 
1214
+ // src/server/mcp-gateway.ts
1215
+ var RoomMcpGateway = class {
1216
+ gateway = new StreamableMcpGateway();
1217
+ async handle(req, ctx, auth, tenantId) {
1218
+ const roomCtx = {
1219
+ pool: ctx.pool,
1220
+ permissions: ctx.permissions,
1221
+ subs: ctx.subs,
1222
+ meter: ctx.meter,
1223
+ auth,
1224
+ tenantId,
1225
+ headerLane: req.headers.get("x-trellis-lane"),
1226
+ headerTenant: req.headers.get("x-trellis-tenant")
1227
+ };
1228
+ return this.gateway.handle(req, () => createRoomMcpServer(roomCtx));
1229
+ }
1230
+ async close() {
1231
+ await this.gateway.close();
1232
+ }
1233
+ };
1234
+ var roomMcpGateway = new RoomMcpGateway();
1235
+
734
1236
  // src/server/server.ts
735
1237
  var __moduleDir = import.meta.dir ?? dirname(fileURLToPath(import.meta.url));
736
1238
  async function startServer(opts) {
@@ -803,6 +1305,7 @@ async function startServerNode(opts) {
803
1305
  return startNodeServer({
804
1306
  port,
805
1307
  fetch: handleHttp,
1308
+ attachPresenceRelay: opts.presenceRelay,
806
1309
  websocket: {
807
1310
  open(ws) {
808
1311
  const id = crypto.randomUUID();
@@ -841,13 +1344,13 @@ async function route(req, url, path, auth, tenantId, ctx) {
841
1344
  const method = req.method.toUpperCase();
842
1345
  if (method === "GET" && path === "/__trellis/inspector.js") {
843
1346
  const candidates = [
844
- join2(__moduleDir, "db", "inspector.js"),
1347
+ join(__moduleDir, "db", "inspector.js"),
845
1348
  // chunk lives in dist/, inspector in dist/db/
846
- join2(__moduleDir, "inspector.js")
1349
+ join(__moduleDir, "inspector.js")
847
1350
  // chunk lives in dist/db/, inspector alongside
848
1351
  ];
849
1352
  for (const p of candidates) {
850
- if (existsSync2(p)) {
1353
+ if (existsSync(p)) {
851
1354
  return new Response(readFileSync(p, "utf-8"), {
852
1355
  headers: { "Content-Type": "application/javascript; charset=utf-8" }
853
1356
  });
@@ -862,11 +1365,11 @@ async function route(req, url, path, auth, tenantId, ctx) {
862
1365
  }
863
1366
  if (method === "GET" && path === "/__trellis/trellis.css") {
864
1367
  const candidates = [
865
- join2(__moduleDir, "db", "trellis.css"),
866
- join2(__moduleDir, "trellis.css")
1368
+ join(__moduleDir, "db", "trellis.css"),
1369
+ join(__moduleDir, "trellis.css")
867
1370
  ];
868
1371
  for (const p of candidates) {
869
- if (existsSync2(p)) {
1372
+ if (existsSync(p)) {
870
1373
  return new Response(readFileSync(p, "utf-8"), {
871
1374
  headers: { "Content-Type": "text/css; charset=utf-8" }
872
1375
  });
@@ -893,13 +1396,38 @@ async function route(req, url, path, auth, tenantId, ctx) {
893
1396
  headers: { "Content-Type": "text/html; charset=utf-8" }
894
1397
  });
895
1398
  }
1399
+ if (path === "/mcp" || path === "/mcp/" || path === "/trellis/mcp" || path === "/trellis/mcp/") {
1400
+ return roomMcpGateway.handle(req, ctx, auth, tenantId);
1401
+ }
1402
+ if (path === "/gateway/mcp" || path === "/gateway/mcp/") {
1403
+ return discoveryMcpGateway.handle(req, {
1404
+ configDir: ".",
1405
+ origin: url.origin
1406
+ });
1407
+ }
1408
+ if (method === "GET" && path === "/.well-known/oauth-protected-resource") {
1409
+ return json(oauthProtectedResourceMetadata(url.origin));
1410
+ }
1411
+ if (method === "GET" && path === "/.well-known/oauth-authorization-server") {
1412
+ return json(oauthAuthorizationServerMetadata(url.origin));
1413
+ }
1414
+ if (method === "GET" && path === "/.well-known/trellis-mcp") {
1415
+ return json(mcpServiceDocument(url.origin, ctx.authConfig));
1416
+ }
896
1417
  if (method === "GET" && path === "/health") {
897
1418
  const kernel = await ctx.pool.preload(tenantId);
898
1419
  const ops = kernel.readAllOps().length;
899
1420
  return json({
900
1421
  status: "ok",
901
1422
  ops,
902
- tenants: ctx.pool.activeTenants().length
1423
+ tenants: ctx.pool.activeTenants().length,
1424
+ mcp: "/mcp",
1425
+ mcpSprites: "/trellis/mcp",
1426
+ mcpGateway: "/gateway/mcp",
1427
+ oauth: {
1428
+ protectedResource: "/.well-known/oauth-protected-resource",
1429
+ authorizationServer: "/.well-known/oauth-authorization-server"
1430
+ }
903
1431
  });
904
1432
  }
905
1433
  if (method === "GET" && path === "/admin/usage") {
@@ -917,12 +1445,20 @@ async function route(req, url, path, auth, tenantId, ctx) {
917
1445
  return handleUpdate(req, id, auth, tenantId, ctx);
918
1446
  if (method === "DELETE") return handleDelete(id, auth, tenantId, ctx);
919
1447
  }
1448
+ if (method === "GET" && (path === "/ops" || path === "/ops/")) {
1449
+ return handleListOps(url, tenantId, ctx);
1450
+ }
920
1451
  if (method === "POST" && path === "/query") {
921
1452
  return handleQuery(req, auth, tenantId, ctx);
922
1453
  }
923
1454
  if (method === "POST" && (path === "/ontologies" || path === "/ontologies/")) {
924
1455
  return handleCreateOntology(req, auth, tenantId, ctx);
925
1456
  }
1457
+ const ontologyMatch = path.match(/^\/ontologies\/(.+)$/);
1458
+ if (ontologyMatch && (method === "PATCH" || method === "PUT")) {
1459
+ const id = decodeURIComponent(ontologyMatch[1]);
1460
+ return handleUpdateOntology(req, id, auth, tenantId, ctx);
1461
+ }
926
1462
  if (method === "POST" && path === "/upload") {
927
1463
  return handleUpload(req, auth, tenantId, ctx);
928
1464
  }
@@ -995,6 +1531,42 @@ async function handleDelete(id, auth, tenantId, ctx) {
995
1531
  await ctx.subs.notify(tenantId);
996
1532
  return json({ id, deleted: true });
997
1533
  }
1534
+ async function handleListOps(url, tenantId, ctx) {
1535
+ const kind = url.searchParams.get("kind") ?? void 0;
1536
+ const limit = Math.min(parseInt(url.searchParams.get("limit") ?? "100", 10), 500);
1537
+ const offset = parseInt(url.searchParams.get("offset") ?? "0", 10);
1538
+ const kernel = await ctx.pool.preload(tenantId);
1539
+ let ops = kernel.readAllOps();
1540
+ if (kind) {
1541
+ ops = ops.filter((op) => op.kind === kind);
1542
+ }
1543
+ const total = ops.length;
1544
+ const page = ops.slice(offset, offset + limit);
1545
+ return json({
1546
+ data: page.map((op, index) => summarizeKernelOp(op, offset + index)),
1547
+ total,
1548
+ limit,
1549
+ offset
1550
+ });
1551
+ }
1552
+ function summarizeKernelOp(op, index) {
1553
+ const entityIds = /* @__PURE__ */ new Set();
1554
+ for (const fact of op.facts ?? []) entityIds.add(fact.e);
1555
+ for (const fact of op.deleteFacts ?? []) entityIds.add(fact.e);
1556
+ return {
1557
+ index,
1558
+ hash: op.hash,
1559
+ kind: op.kind,
1560
+ timestamp: op.timestamp,
1561
+ agentId: op.agentId,
1562
+ previousHash: op.previousHash,
1563
+ factCount: op.facts?.length ?? 0,
1564
+ linkCount: op.links?.length ?? 0,
1565
+ deleteFactCount: op.deleteFacts?.length ?? 0,
1566
+ deleteLinkCount: op.deleteLinks?.length ?? 0,
1567
+ entityCount: entityIds.size
1568
+ };
1569
+ }
998
1570
  async function handleList(url, auth, tenantId, ctx) {
999
1571
  const type = url.searchParams.get("type") ?? void 0;
1000
1572
  const limit = parseInt(url.searchParams.get("limit") ?? "100");
@@ -1111,6 +1683,28 @@ async function handleCreateOntology(req, auth, tenantId, ctx) {
1111
1683
  }
1112
1684
  return json({ id: schema["@id"], registered: true }, 201);
1113
1685
  }
1686
+ async function handleUpdateOntology(req, id, auth, tenantId, ctx) {
1687
+ const parsed = await readJsonObject(req, "/ontologies");
1688
+ if (!parsed.ok) return parsed.response;
1689
+ const updates = parsed.data;
1690
+ const kernel = await ctx.pool.preload(tenantId);
1691
+ const existing = kernel.getOntology(id);
1692
+ if (!existing) {
1693
+ return json({ error: `Ontology ${id} not found` }, 404);
1694
+ }
1695
+ if ((existing.tier ?? "user") === "core") {
1696
+ return json({ error: "Core ontologies are immutable" }, 403);
1697
+ }
1698
+ try {
1699
+ kernel.updateOntology(id, updates);
1700
+ recordGraphIo(ctx, tenantId);
1701
+ } catch (err) {
1702
+ const msg = err instanceof Error ? err.message : String(err);
1703
+ return json({ error: "Could not update schema", message: msg }, 400);
1704
+ }
1705
+ const updated = kernel.getOntology(id);
1706
+ return json(updated ?? { id, updated: true });
1707
+ }
1114
1708
  async function handleUpload(req, auth, tenantId, ctx) {
1115
1709
  if (!auth.authenticated && ctx.config.apiKey) {
1116
1710
  return json({ error: "Unauthorized" }, 401);
@@ -1348,11 +1942,6 @@ export {
1348
1942
  FULLY_PUBLIC,
1349
1943
  OWNER_ONLY,
1350
1944
  ADMIN_ONLY,
1351
- resolveUsageTenantId,
1352
- dayKey,
1353
- sampleTenantStorage,
1354
- verifyAdminKey,
1355
- UsageMeter,
1356
1945
  SubscriptionManager,
1357
1946
  startServer,
1358
1947
  startServerCrossRuntime