@unson/brainbase-mcp 0.5.0 → 0.7.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.
Files changed (92) hide show
  1. package/README.md +8 -0
  2. package/contracts/judgment-dag/digest.json +2 -2
  3. package/dist/canonical-task-contract.d.ts +9 -0
  4. package/dist/canonical-task-contract.js +39 -0
  5. package/dist/canonical-task-principal.d.ts +16 -0
  6. package/dist/canonical-task-principal.js +36 -0
  7. package/dist/canonical-task-service.d.ts +251 -0
  8. package/dist/canonical-task-service.js +809 -0
  9. package/dist/company-os-evaluation.d.ts +193 -0
  10. package/dist/company-os-evaluation.js +1150 -0
  11. package/dist/company-os-impact-review.d.ts +248 -0
  12. package/dist/company-os-impact-review.js +663 -0
  13. package/dist/company-os-learning-adoption.d.ts +288 -0
  14. package/dist/company-os-learning-adoption.js +944 -0
  15. package/dist/company-os-objectives.d.ts +52 -0
  16. package/dist/company-os-objectives.js +197 -0
  17. package/dist/company-os-receipt-adapter.d.ts +109 -0
  18. package/dist/company-os-receipt-adapter.js +636 -0
  19. package/dist/constraint-exception-store.d.ts +23 -0
  20. package/dist/constraint-exception-store.js +108 -0
  21. package/dist/constraint-resolution.d.ts +175 -0
  22. package/dist/constraint-resolution.js +922 -0
  23. package/dist/decision-adapter.d.ts +145 -0
  24. package/dist/decision-adapter.js +586 -0
  25. package/dist/durable-wait-http.d.ts +51 -0
  26. package/dist/durable-wait-http.js +497 -0
  27. package/dist/durable-waits.d.ts +234 -0
  28. package/dist/durable-waits.js +944 -0
  29. package/dist/execution-authority-http.d.ts +28 -0
  30. package/dist/execution-authority-http.js +359 -0
  31. package/dist/execution-authority.d.ts +201 -0
  32. package/dist/execution-authority.js +634 -0
  33. package/dist/foundation-catalog.d.ts +26 -0
  34. package/dist/foundation-catalog.js +206 -0
  35. package/dist/foundation-constraint-store.d.ts +50 -0
  36. package/dist/foundation-constraint-store.js +215 -0
  37. package/dist/foundation-http.d.ts +90 -0
  38. package/dist/foundation-http.js +439 -0
  39. package/dist/foundation-store.d.ts +132 -0
  40. package/dist/foundation-store.js +654 -0
  41. package/dist/judgment-dag-composition.d.ts +224 -0
  42. package/dist/judgment-dag-composition.js +731 -0
  43. package/dist/judgment-dag.d.ts +2 -0
  44. package/dist/judgment-dag.js +1 -0
  45. package/dist/judgment-problem-snapshot.d.ts +185 -0
  46. package/dist/judgment-problem-snapshot.js +777 -0
  47. package/dist/judgment-view-http.d.ts +22 -0
  48. package/dist/judgment-view-http.js +128 -0
  49. package/dist/judgment-view.d.ts +222 -0
  50. package/dist/judgment-view.js +627 -0
  51. package/dist/knowledge-adapter.d.ts +112 -0
  52. package/dist/knowledge-adapter.js +691 -0
  53. package/dist/ontology-foundation.d.ts +209 -0
  54. package/dist/ontology-foundation.js +1000 -0
  55. package/dist/ontology.d.ts +13 -0
  56. package/dist/ontology.js +14 -0
  57. package/dist/organization-connection.d.ts +164 -0
  58. package/dist/organization-connection.js +428 -0
  59. package/dist/personal-knowledge.d.ts +120 -0
  60. package/dist/personal-knowledge.js +750 -0
  61. package/dist/problem-candidates.d.ts +149 -0
  62. package/dist/problem-candidates.js +898 -0
  63. package/dist/problem-selection.d.ts +238 -0
  64. package/dist/problem-selection.js +995 -0
  65. package/dist/resource-reservation-http.d.ts +31 -0
  66. package/dist/resource-reservation-http.js +371 -0
  67. package/dist/resource-reservations.d.ts +221 -0
  68. package/dist/resource-reservations.js +577 -0
  69. package/dist/server.d.ts +97 -0
  70. package/dist/server.js +180 -1
  71. package/dist/ssot.d.ts +6 -1
  72. package/dist/ssot.js +63 -5
  73. package/dist/streamable-http.d.ts +42 -0
  74. package/dist/streamable-http.js +288 -0
  75. package/dist/types.d.ts +20 -0
  76. package/dist/world-model.d.ts +181 -0
  77. package/dist/world-model.js +873 -0
  78. package/package.json +145 -3
  79. package/ui/icons/mana/LICENSE.tabler.txt +21 -0
  80. package/ui/icons/mana/affiliate.svg +20 -0
  81. package/ui/icons/mana/brand-github.svg +15 -0
  82. package/ui/icons/mana/brand-google-drive.svg +17 -0
  83. package/ui/icons/mana/brand-slack.svg +18 -0
  84. package/ui/icons/mana/cpu.svg +24 -0
  85. package/ui/judgment-view.css +238 -0
  86. package/ui/judgment-view.js +508 -0
  87. package/ui/objective-editor.css +180 -0
  88. package/ui/objective-editor.js +1139 -0
  89. package/ui/outcome-knowledge.css +365 -0
  90. package/ui/outcome-knowledge.js +1570 -0
  91. package/ui/outcome-mana.css +255 -0
  92. package/ui/outcome-mana.js +2325 -0
package/dist/server.js CHANGED
@@ -16,6 +16,7 @@ import { auditPersonalOsDirectory } from './ontology-ssot.js';
16
16
  import { getOntologyImpact, inferPersonalOs, portableOntology, resolveOntologyVersion } from './ontology.js';
17
17
  import { loadPersonalOs } from './ssot.js';
18
18
  import { getContext, listEntities, onboardingStatus, searchAll, searchPersonalKg } from './tools.js';
19
+ import { createLocalPersonalKnowledgeStore, createPersonalKnowledgeClient, decodePersonalKnowledgeContextHeader, PERSONAL_KNOWLEDGE_CONTRACT_VERSION, validatePersonalKnowledgeContext } from './personal-knowledge.js';
19
20
  const argsSchema = z.object({
20
21
  dataDir: z.string().optional(),
21
22
  query: z.string().optional(),
@@ -35,6 +36,29 @@ const argsSchema = z.object({
35
36
  mode: z.never().optional()
36
37
  });
37
38
  const searchArgsSchema = argsSchema.pick({ dataDir: true, query: true, limit: true, project: true, as_of: true, asOf: true, seedIds: true, steps: true, mode: true }).strict();
39
+ const personalKnowledgeEventSchema = z.object({
40
+ event_id: z.string().min(1),
41
+ body: z.string().optional(),
42
+ content: z.string().optional(),
43
+ body_hash: z.string().min(1).optional(),
44
+ occurred_at: z.string().min(1).optional(),
45
+ captured_at: z.string().min(1).optional(),
46
+ source: z.unknown().optional(),
47
+ source_pointer: z.unknown().optional(),
48
+ parent_episode_id: z.string().min(1).nullable().optional(),
49
+ permission_snapshot: z.record(z.unknown()).optional(),
50
+ sensitivity: z.string().min(1).optional(),
51
+ kind: z.string().min(1).optional(),
52
+ type: z.string().min(1).optional(),
53
+ tags: z.array(z.string().min(1)).optional(),
54
+ metadata: z.record(z.unknown()).optional()
55
+ }).strict();
56
+ const personalKnowledgeContextSchema = z.object({}).strict();
57
+ const personalKnowledgeRegisterSchema = z.object({ event: personalKnowledgeEventSchema }).strict();
58
+ const personalKnowledgeSearchSchema = z.object({
59
+ query: z.string(),
60
+ limit: z.number().int().min(1).max(50).optional()
61
+ }).strict();
38
62
  const mentionSpanSchema = z.object({
39
63
  start: z.number().int().nonnegative(),
40
64
  end: z.number().int().positive()
@@ -187,6 +211,61 @@ export const toolDefinitions = [
187
211
  }
188
212
  }
189
213
  },
214
+ {
215
+ name: 'personal_knowledge_context',
216
+ description: 'Return the authenticated v1 personal knowledge context. Identity and storage selection come from trusted configuration, not tool arguments.',
217
+ inputSchema: {
218
+ type: 'object',
219
+ additionalProperties: false,
220
+ properties: {}
221
+ }
222
+ },
223
+ {
224
+ name: 'personal_knowledge_register',
225
+ description: 'Register one v1 personal knowledge event in the explicitly configured local or managed context. Caller identity, organization, source, and path are never accepted as event fields.',
226
+ inputSchema: {
227
+ type: 'object',
228
+ required: ['event'],
229
+ additionalProperties: false,
230
+ properties: {
231
+ event: {
232
+ type: 'object',
233
+ required: ['event_id'],
234
+ additionalProperties: false,
235
+ properties: {
236
+ event_id: { type: 'string', minLength: 1 },
237
+ body: { type: 'string' },
238
+ content: { type: 'string' },
239
+ body_hash: { type: 'string', minLength: 1 },
240
+ occurred_at: { type: 'string', minLength: 1 },
241
+ captured_at: { type: 'string', minLength: 1 },
242
+ source: {},
243
+ source_pointer: {},
244
+ parent_episode_id: { type: ['string', 'null'] },
245
+ permission_snapshot: { type: 'object' },
246
+ sensitivity: { type: 'string', minLength: 1 },
247
+ kind: { type: 'string', minLength: 1 },
248
+ type: { type: 'string', minLength: 1 },
249
+ tags: { type: 'array', items: { type: 'string', minLength: 1 } },
250
+ metadata: { type: 'object' }
251
+ }
252
+ }
253
+ }
254
+ }
255
+ },
256
+ {
257
+ name: 'personal_knowledge_search',
258
+ description: 'Search the explicitly configured v1 personal knowledge context. The result includes the verified context and never falls back to another store.',
259
+ inputSchema: {
260
+ type: 'object',
261
+ required: ['query'],
262
+ additionalProperties: false,
263
+ properties: {
264
+ query: { type: 'string', minLength: 1, maxLength: 4000 },
265
+ limit: { type: 'integer', minimum: 1, maximum: 50 }
266
+ }
267
+ }
268
+ },
190
269
  {
191
270
  name: 'onboarding_status',
192
271
  description: 'Report seeded areas, first value demo readiness, missing setup, and local connection status.',
@@ -351,6 +430,29 @@ export const toolDefinitions = [
351
430
  }
352
431
  }
353
432
  ];
433
+ const managedPersonalKnowledgeToolNames = new Set([
434
+ 'personal_knowledge_context',
435
+ 'personal_knowledge_register',
436
+ 'personal_knowledge_search'
437
+ ]);
438
+ function isPersonalKnowledgeTool(name) {
439
+ return managedPersonalKnowledgeToolNames.has(name);
440
+ }
441
+ function configuredPersonalKnowledgeMode() {
442
+ const configuredMode = process.env.BRAINBASE_PERSONAL_KNOWLEDGE_MODE?.trim()
443
+ || process.env.BRAINBASE_PERSONAL_KG_STORAGE_MODE?.trim()
444
+ || 'local';
445
+ if (configuredMode === 'local' || configuredMode === 'managed_cloud') {
446
+ return configuredMode;
447
+ }
448
+ throw new Error(`Unsupported Personal Knowledge mode: ${configuredMode}.`);
449
+ }
450
+ function configuredToolDefinitions() {
451
+ if (configuredPersonalKnowledgeMode() === 'managed_cloud') {
452
+ return toolDefinitions.filter((tool) => isPersonalKnowledgeTool(tool.name));
453
+ }
454
+ return [...toolDefinitions];
455
+ }
354
456
  let embeddingProvider;
355
457
  let embeddingProviderInitialized = false;
356
458
  function configuredEmbeddingProvider() {
@@ -360,7 +462,84 @@ function configuredEmbeddingProvider() {
360
462
  }
361
463
  return embeddingProvider;
362
464
  }
465
+ function configuredPersonalKnowledgeContext() {
466
+ const encoded = process.env.BRAINBASE_PERSONAL_KNOWLEDGE_CONTEXT?.trim()
467
+ || process.env.BRAINBASE_PERSONAL_CONTEXT?.trim();
468
+ if (encoded)
469
+ return decodePersonalKnowledgeContextHeader(encoded);
470
+ const scope = process.env.BRAINBASE_PERSONAL_KNOWLEDGE_SCOPE?.trim()
471
+ || process.env.BRAINBASE_PERSONAL_SCOPE?.trim()
472
+ || 'organization_private';
473
+ const owner = process.env.BRAINBASE_PERSONAL_KNOWLEDGE_OWNER_PERSON_ID?.trim()
474
+ || process.env.BRAINBASE_PERSON_ID?.trim();
475
+ const source = process.env.BRAINBASE_PERSONAL_KNOWLEDGE_SOURCE_ID?.trim()
476
+ || process.env.BRAINBASE_PERSONAL_SOURCE_ID?.trim();
477
+ const organization = process.env.BRAINBASE_PERSONAL_KNOWLEDGE_ORGANIZATION_ID?.trim()
478
+ || process.env.BRAINBASE_ORGANIZATION_ID?.trim();
479
+ if (!owner || !source) {
480
+ throw new Error('Managed Personal Knowledge requires owner person and source configuration.');
481
+ }
482
+ return validatePersonalKnowledgeContext({
483
+ contract_version: PERSONAL_KNOWLEDGE_CONTRACT_VERSION,
484
+ scope,
485
+ owner_person_id: owner,
486
+ organization_id: scope === 'personal_owned' ? null : organization,
487
+ source_id: source
488
+ });
489
+ }
490
+ function configuredPersonalKnowledgeRuntime() {
491
+ const configuredMode = configuredPersonalKnowledgeMode();
492
+ if (configuredMode === 'local') {
493
+ const encodedContext = process.env.BRAINBASE_PERSONAL_KNOWLEDGE_CONTEXT?.trim()
494
+ || process.env.BRAINBASE_PERSONAL_CONTEXT?.trim();
495
+ if (encodedContext) {
496
+ const context = decodePersonalKnowledgeContextHeader(encodedContext);
497
+ if (context.scope !== 'personal_owned' || context.organization_id !== null) {
498
+ throw new Error('OSS local Personal Knowledge requires a personal_owned context.');
499
+ }
500
+ return createLocalPersonalKnowledgeStore({ context });
501
+ }
502
+ return createLocalPersonalKnowledgeStore();
503
+ }
504
+ const apiUrl = process.env.BRAINBASE_PERSONAL_KNOWLEDGE_API_URL?.trim()
505
+ || process.env.BRAINBASE_PERSONAL_KG_MANAGED_CLOUD_API_URL?.trim();
506
+ if (!apiUrl)
507
+ throw new Error('Managed Personal Knowledge requires an explicit API URL.');
508
+ const token = process.env.BRAINBASE_PERSONAL_KNOWLEDGE_TOKEN
509
+ || process.env.BRAINBASE_PERSONAL_KG_TOKEN;
510
+ if (!token?.trim())
511
+ throw new Error('Managed Personal Knowledge requires an authenticated token.');
512
+ return createPersonalKnowledgeClient({
513
+ mode: 'managed_cloud',
514
+ apiUrl,
515
+ expectedContext: configuredPersonalKnowledgeContext(),
516
+ token
517
+ });
518
+ }
519
+ async function callPersonalKnowledgeTool(name, rawArgs) {
520
+ const runtime = configuredPersonalKnowledgeRuntime();
521
+ if (name === 'personal_knowledge_context') {
522
+ personalKnowledgeContextSchema.parse(rawArgs ?? {});
523
+ return runtime.getContext();
524
+ }
525
+ if (name === 'personal_knowledge_register') {
526
+ const args = personalKnowledgeRegisterSchema.parse(rawArgs ?? {});
527
+ return runtime.register(args.event);
528
+ }
529
+ if (name === 'personal_knowledge_search') {
530
+ const args = personalKnowledgeSearchSchema.parse(rawArgs ?? {});
531
+ return runtime.search(args.query, args.limit);
532
+ }
533
+ throw new Error(`Unknown Personal Knowledge tool: ${name}`);
534
+ }
363
535
  export async function callBrainbaseTool(name, rawArgs = {}) {
536
+ const mode = configuredPersonalKnowledgeMode();
537
+ if (mode === 'managed_cloud' && !isPersonalKnowledgeTool(name)) {
538
+ throw new Error('managed_cloud Personal Knowledge MCP only permits personal_knowledge_context, personal_knowledge_register, and personal_knowledge_search.');
539
+ }
540
+ if (isPersonalKnowledgeTool(name)) {
541
+ return callPersonalKnowledgeTool(name, rawArgs);
542
+ }
364
543
  if (name in connectedSchemas) {
365
544
  return callConnectedOnboardingTool(name, rawArgs);
366
545
  }
@@ -688,7 +867,7 @@ export function createServer() {
688
867
  }
689
868
  });
690
869
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
691
- tools: [...toolDefinitions]
870
+ tools: configuredToolDefinitions()
692
871
  }));
693
872
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
694
873
  const result = await callBrainbaseTool(request.params.name, request.params.arguments);
package/dist/ssot.d.ts CHANGED
@@ -3,7 +3,7 @@ import type { PersonalOs } from './types.js';
3
3
  export declare function initializePersonalOs(dataDir: string): Promise<void>;
4
4
  export declare function loadPersonalOs(dataDir: string): Promise<PersonalOs>;
5
5
  export declare function mutatePersonalOs(dataDir: string, mutator: (current: PersonalOs) => PersonalOs | Promise<PersonalOs>): Promise<PersonalOs>;
6
- export declare function mutatePersonalOsWithSidecar<T>(dataDir: string, sidecarPath: string, mutator: (current: PersonalOs) => {
6
+ export declare function mutatePersonalOsWithSidecar<T>(dataDir: string, sidecarPath: string, mutator: (current: PersonalOs, sidecarContent?: string) => {
7
7
  next: PersonalOs;
8
8
  sidecarContent: string;
9
9
  result: T;
@@ -12,6 +12,11 @@ export declare function mutatePersonalOsWithSidecar<T>(dataDir: string, sidecarP
12
12
  sidecarContent: string;
13
13
  result: T;
14
14
  }>): Promise<T>;
15
+ /**
16
+ * Reads a sidecar while holding the canonical SSOT lock. A missing sidecar is
17
+ * returned as undefined so a first mutation can create it transactionally.
18
+ */
19
+ export declare function readPersonalOsSidecar(dataDir: string, sidecarPath: string): Promise<string | undefined>;
15
20
  export type CanonicalGraphMigrationExecution = CanonicalGraphMigrationPlan & {
16
21
  expectedInputDigest: string;
17
22
  written: boolean;
package/dist/ssot.js CHANGED
@@ -5,6 +5,7 @@ import { hostname } from 'node:os';
5
5
  import { dirname, isAbsolute, join, relative, resolve, win32 } from 'node:path';
6
6
  import { z } from 'zod';
7
7
  import { validateCanonicalGraph } from './canonical-graph.js';
8
+ import { assertFoundationCatalog } from './foundation-catalog.js';
8
9
  import { assertOntologyValid } from './ontology.js';
9
10
  import { planCanonicalGraphMigration } from './ontology-migration.js';
10
11
  import { emptyGraph, emptyRelationships, schemaTemplates } from './templates.js';
@@ -91,13 +92,25 @@ export async function mutatePersonalOsWithSidecar(dataDir, sidecarPath, mutator)
91
92
  await recoverTransactions(dataDir);
92
93
  assertCompleteCanonicalSet(dataDir, await canonicalPresence(dataDir));
93
94
  const current = await loadPersonalOsUnlocked(dataDir);
94
- const mutation = await mutator(current);
95
+ const mutation = await mutator(current, await readOptionalSidecar(dataDir, sidecarPath));
95
96
  const normalized = { ...mutation.next, dataDir, sourceCount: current.sourceCount };
96
97
  validateAggregate(normalized);
97
98
  await commitAggregate(dataDir, normalized, 'mutation', [{ relativePath: sidecarPath, content: mutation.sidecarContent }]);
98
99
  return mutation.result;
99
100
  });
100
101
  }
102
+ /**
103
+ * Reads a sidecar while holding the canonical SSOT lock. A missing sidecar is
104
+ * returned as undefined so a first mutation can create it transactionally.
105
+ */
106
+ export async function readPersonalOsSidecar(dataDir, sidecarPath) {
107
+ assertSafeSidecarPath(sidecarPath);
108
+ return withSsotLock(dataDir, async () => {
109
+ await recoverTransactions(dataDir);
110
+ assertCompleteCanonicalSet(dataDir, await canonicalPresence(dataDir));
111
+ return readOptionalSidecar(dataDir, sidecarPath);
112
+ });
113
+ }
101
114
  /**
102
115
  * Plans or atomically applies the canonical Graph migration.
103
116
  *
@@ -172,11 +185,22 @@ async function commitAggregate(dataDir, next, mode, sidecars = []) {
172
185
  await mkdir(previousDir, { recursive: true });
173
186
  await copyCanonicalSet(dataDir, previousDir);
174
187
  for (const sidecar of sidecars) {
175
- await mkdir(dirname(join(previousDir, sidecar.relativePath)), { recursive: true });
176
- await copyFile(join(dataDir, sidecar.relativePath), join(previousDir, sidecar.relativePath));
188
+ if (await exists(join(dataDir, sidecar.relativePath))) {
189
+ await mkdir(dirname(join(previousDir, sidecar.relativePath)), { recursive: true });
190
+ await copyFile(join(dataDir, sidecar.relativePath), join(previousDir, sidecar.relativePath));
191
+ }
177
192
  }
178
193
  }
179
- const metadata = { version: 1, mode, ...(sidecars.length > 0 ? { sidecarFiles: sidecars.map((item) => item.relativePath) } : {}) };
194
+ const sidecarFiles = sidecars.map((item) => item.relativePath);
195
+ const previousSidecarFiles = mode === 'mutation'
196
+ ? (await Promise.all(sidecars.map(async (item) => (await exists(join(dataDir, item.relativePath))) ? item.relativePath : undefined))).filter((item) => item !== undefined)
197
+ : [];
198
+ const metadata = {
199
+ version: 1,
200
+ mode,
201
+ ...(sidecarFiles.length > 0 ? { sidecarFiles } : {}),
202
+ ...(mode === 'mutation' && sidecarFiles.length > 0 ? { previousSidecarFiles } : {})
203
+ };
180
204
  await writeFile(join(stagingDir, 'transaction.json'), `${JSON.stringify(metadata, null, 2)}\n`);
181
205
  await writeFile(join(stagingDir, 'PREPARED'), '');
182
206
  await rename(stagingDir, transactionDir);
@@ -224,7 +248,13 @@ async function recoverTransaction(dataDir, transactionDir) {
224
248
  if (process.env.BRAINBASE_SSOT_FAIL_RECOVERY === '1') {
225
249
  throw new Error(`Injected SSOT transaction recovery failure for ${transactionDir}`);
226
250
  }
227
- await publishCanonicalSet(dataDir, retainedDir, `recovery-${randomUUID()}`, false, metadata.sidecarFiles ?? []);
251
+ const previousSidecarFiles = metadata.mode === 'initialization'
252
+ ? metadata.sidecarFiles ?? []
253
+ : metadata.previousSidecarFiles ?? metadata.sidecarFiles ?? [];
254
+ await publishCanonicalSet(dataDir, retainedDir, `recovery-${randomUUID()}`, false, previousSidecarFiles);
255
+ if (metadata.mode === 'mutation') {
256
+ await removeSidecars(dataDir, (metadata.sidecarFiles ?? []).filter((path) => !previousSidecarFiles.includes(path)));
257
+ }
228
258
  await rm(transactionDir, { recursive: true, force: true });
229
259
  }
230
260
  async function publishCanonicalSet(dataDir, retainedDir, token, allowInjectedFailure, sidecarFiles = []) {
@@ -256,6 +286,12 @@ async function publishCanonicalSet(dataDir, retainedDir, token, allowInjectedFai
256
286
  }
257
287
  }
258
288
  }
289
+ async function removeSidecars(dataDir, relativePaths) {
290
+ for (const relativePath of relativePaths) {
291
+ assertSafeSidecarPath(relativePath);
292
+ await rm(join(dataDir, relativePath), { force: true });
293
+ }
294
+ }
259
295
  async function writeAggregate(targetDir, os) {
260
296
  await writeFile(join(targetDir, 'graph.json'), `${JSON.stringify(os.graph, null, 2)}\n`);
261
297
  await writeFile(join(targetDir, 'relationships.json'), `${JSON.stringify(os.relationships, null, 2)}\n`);
@@ -268,6 +304,8 @@ function validateAggregate(os) {
268
304
  os.decisions.forEach((decision) => decisionSchema.parse(decision));
269
305
  assertOntologyValid(os);
270
306
  validateCanonicalGraph(os.graph);
307
+ if (os.graph.version === 2)
308
+ assertFoundationCatalog(os.graph.foundation);
271
309
  }
272
310
  function parseGraph(value) {
273
311
  try {
@@ -283,8 +321,13 @@ function parseGraph(value) {
283
321
  }
284
322
  validateCanonicalGraph(value, { allowDuplicateEntityIds: true });
285
323
  }
324
+ if (isGraphV2(value))
325
+ assertFoundationCatalog(value.foundation);
286
326
  return value;
287
327
  }
328
+ function isGraphV2(value) {
329
+ return typeof value === 'object' && value !== null && value.version === 2;
330
+ }
288
331
  function serializeJsonl(values) {
289
332
  return values.length === 0 ? '' : `${values.map((value) => JSON.stringify(value)).join('\n')}\n`;
290
333
  }
@@ -405,8 +448,13 @@ async function readTransactionMetadata(transactionDir) {
405
448
  if (value.sidecarFiles !== undefined && (!Array.isArray(value.sidecarFiles) || value.sidecarFiles.some((path) => typeof path !== 'string'))) {
406
449
  throw new Error(`Invalid registered SSOT transaction sidecars in ${transactionDir}`);
407
450
  }
451
+ if (value.previousSidecarFiles !== undefined && (!Array.isArray(value.previousSidecarFiles) || value.previousSidecarFiles.some((path) => typeof path !== 'string'))) {
452
+ throw new Error(`Invalid registered SSOT transaction previous sidecars in ${transactionDir}`);
453
+ }
408
454
  for (const path of value.sidecarFiles ?? [])
409
455
  assertSafeSidecarPath(path);
456
+ for (const path of value.previousSidecarFiles ?? [])
457
+ assertSafeSidecarPath(path);
410
458
  return value;
411
459
  }
412
460
  function assertSafeSidecarPath(relativePath) {
@@ -439,6 +487,16 @@ async function exists(path) {
439
487
  return false;
440
488
  }
441
489
  }
490
+ async function readOptionalSidecar(dataDir, sidecarPath) {
491
+ try {
492
+ return await readFile(join(dataDir, sidecarPath), 'utf8');
493
+ }
494
+ catch (error) {
495
+ if (error.code === 'ENOENT')
496
+ return undefined;
497
+ throw error;
498
+ }
499
+ }
442
500
  async function readJson(filePath) {
443
501
  try {
444
502
  return JSON.parse(await readFile(filePath, 'utf8'));
@@ -0,0 +1,42 @@
1
+ import { type IncomingMessage, type Server as HttpServer, type ServerResponse } from 'node:http';
2
+ import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
3
+ import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
+ type MaybePromise<T> = T | Promise<T>;
5
+ export interface StreamableHttpRequestScope<Auth = AuthInfo> {
6
+ requestId: string;
7
+ method: string;
8
+ path: string;
9
+ auth?: Auth;
10
+ sessionId?: string;
11
+ }
12
+ export interface StreamableHttpRequestScopeInput<Auth = AuthInfo> {
13
+ request: IncomingMessage;
14
+ requestId: string;
15
+ auth?: Auth;
16
+ sessionId?: string;
17
+ }
18
+ export interface StreamableHttpHealthScope {
19
+ request: IncomingMessage;
20
+ requestId: string;
21
+ }
22
+ export interface StreamableHttpDependencies<Auth = AuthInfo> {
23
+ /** Return null to reject the request. Undefined means anonymous access. */
24
+ authenticate?: (request: IncomingMessage) => MaybePromise<Auth | null>;
25
+ /** Build one MCP server for each initialized MCP session. */
26
+ createServer: (scope: StreamableHttpRequestScope<Auth>) => MaybePromise<Server>;
27
+ /** Add application request context without coupling the transport to an app. */
28
+ requestScope?: (input: StreamableHttpRequestScopeInput<Auth>) => MaybePromise<Partial<StreamableHttpRequestScope<Auth>>>;
29
+ /** Health is deliberately independent from MCP authentication and application routes. */
30
+ health?: (scope: StreamableHttpHealthScope) => MaybePromise<unknown>;
31
+ bodyLimitBytes?: number;
32
+ mcpPath?: string;
33
+ healthPath?: string;
34
+ }
35
+ export interface StreamableHttpHandler {
36
+ handle(request: IncomingMessage, response: ServerResponse): Promise<void>;
37
+ close(): Promise<void>;
38
+ readonly activeSessionCount: number;
39
+ }
40
+ export declare function createStreamableHttpHandler<Auth = AuthInfo>(dependencies: StreamableHttpDependencies<Auth>): StreamableHttpHandler;
41
+ export declare function createStreamableHttpServer<Auth = AuthInfo>(dependencies: StreamableHttpDependencies<Auth>): HttpServer;
42
+ export {};