@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.
- package/README.md +8 -0
- package/contracts/judgment-dag/digest.json +2 -2
- package/dist/canonical-task-contract.d.ts +9 -0
- package/dist/canonical-task-contract.js +39 -0
- package/dist/canonical-task-principal.d.ts +16 -0
- package/dist/canonical-task-principal.js +36 -0
- package/dist/canonical-task-service.d.ts +251 -0
- package/dist/canonical-task-service.js +809 -0
- package/dist/company-os-evaluation.d.ts +193 -0
- package/dist/company-os-evaluation.js +1150 -0
- package/dist/company-os-impact-review.d.ts +248 -0
- package/dist/company-os-impact-review.js +663 -0
- package/dist/company-os-learning-adoption.d.ts +288 -0
- package/dist/company-os-learning-adoption.js +944 -0
- package/dist/company-os-objectives.d.ts +52 -0
- package/dist/company-os-objectives.js +197 -0
- package/dist/company-os-receipt-adapter.d.ts +109 -0
- package/dist/company-os-receipt-adapter.js +636 -0
- package/dist/constraint-exception-store.d.ts +23 -0
- package/dist/constraint-exception-store.js +108 -0
- package/dist/constraint-resolution.d.ts +175 -0
- package/dist/constraint-resolution.js +922 -0
- package/dist/decision-adapter.d.ts +145 -0
- package/dist/decision-adapter.js +586 -0
- package/dist/durable-wait-http.d.ts +51 -0
- package/dist/durable-wait-http.js +497 -0
- package/dist/durable-waits.d.ts +234 -0
- package/dist/durable-waits.js +944 -0
- package/dist/execution-authority-http.d.ts +28 -0
- package/dist/execution-authority-http.js +359 -0
- package/dist/execution-authority.d.ts +201 -0
- package/dist/execution-authority.js +634 -0
- package/dist/foundation-catalog.d.ts +26 -0
- package/dist/foundation-catalog.js +206 -0
- package/dist/foundation-constraint-store.d.ts +50 -0
- package/dist/foundation-constraint-store.js +215 -0
- package/dist/foundation-http.d.ts +90 -0
- package/dist/foundation-http.js +439 -0
- package/dist/foundation-store.d.ts +132 -0
- package/dist/foundation-store.js +654 -0
- package/dist/judgment-dag-composition.d.ts +224 -0
- package/dist/judgment-dag-composition.js +731 -0
- package/dist/judgment-dag.d.ts +2 -0
- package/dist/judgment-dag.js +1 -0
- package/dist/judgment-problem-snapshot.d.ts +185 -0
- package/dist/judgment-problem-snapshot.js +777 -0
- package/dist/judgment-view-http.d.ts +22 -0
- package/dist/judgment-view-http.js +128 -0
- package/dist/judgment-view.d.ts +222 -0
- package/dist/judgment-view.js +627 -0
- package/dist/knowledge-adapter.d.ts +112 -0
- package/dist/knowledge-adapter.js +691 -0
- package/dist/ontology-foundation.d.ts +209 -0
- package/dist/ontology-foundation.js +1000 -0
- package/dist/ontology.d.ts +13 -0
- package/dist/ontology.js +14 -0
- package/dist/organization-connection.d.ts +164 -0
- package/dist/organization-connection.js +428 -0
- package/dist/personal-knowledge.d.ts +120 -0
- package/dist/personal-knowledge.js +750 -0
- package/dist/problem-candidates.d.ts +149 -0
- package/dist/problem-candidates.js +898 -0
- package/dist/problem-selection.d.ts +238 -0
- package/dist/problem-selection.js +995 -0
- package/dist/resource-reservation-http.d.ts +31 -0
- package/dist/resource-reservation-http.js +371 -0
- package/dist/resource-reservations.d.ts +221 -0
- package/dist/resource-reservations.js +577 -0
- package/dist/server.d.ts +97 -0
- package/dist/server.js +180 -1
- package/dist/ssot.d.ts +6 -1
- package/dist/ssot.js +63 -5
- package/dist/streamable-http.d.ts +42 -0
- package/dist/streamable-http.js +288 -0
- package/dist/types.d.ts +20 -0
- package/dist/world-model.d.ts +181 -0
- package/dist/world-model.js +873 -0
- package/package.json +145 -3
- package/ui/icons/mana/LICENSE.tabler.txt +21 -0
- package/ui/icons/mana/affiliate.svg +20 -0
- package/ui/icons/mana/brand-github.svg +15 -0
- package/ui/icons/mana/brand-google-drive.svg +17 -0
- package/ui/icons/mana/brand-slack.svg +18 -0
- package/ui/icons/mana/cpu.svg +24 -0
- package/ui/judgment-view.css +238 -0
- package/ui/judgment-view.js +508 -0
- package/ui/objective-editor.css +180 -0
- package/ui/objective-editor.js +1139 -0
- package/ui/outcome-knowledge.css +365 -0
- package/ui/outcome-knowledge.js +1570 -0
- package/ui/outcome-mana.css +255 -0
- 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:
|
|
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
|
|
176
|
-
|
|
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
|
|
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
|
-
|
|
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 {};
|