@unson/brainbase-mcp 0.7.0 → 0.9.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 (91) hide show
  1. package/README.md +1 -1
  2. package/contracts/foundation/graph-history.sql +634 -0
  3. package/contracts/judgment-dag/digest.json +2 -2
  4. package/contracts/judgment-value-proof/schema.json +43 -1
  5. package/dist/canonical-task-service.js +16 -5
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +107 -0
  8. package/dist/company-os-objectives.d.ts +11 -0
  9. package/dist/company-os-objectives.js +84 -47
  10. package/dist/foundation-authenticated-tools.d.ts +56 -0
  11. package/dist/foundation-authenticated-tools.js +347 -0
  12. package/dist/foundation-graph-http.d.ts +43 -0
  13. package/dist/foundation-graph-http.js +243 -0
  14. package/dist/foundation-graph-write.d.ts +90 -0
  15. package/dist/foundation-graph-write.js +447 -0
  16. package/dist/foundation-public-provider.d.ts +140 -0
  17. package/dist/foundation-public-provider.js +192 -0
  18. package/dist/graph-corrections.d.ts +157 -0
  19. package/dist/graph-corrections.js +620 -0
  20. package/dist/graph-foundation-reader.d.ts +60 -0
  21. package/dist/graph-foundation-reader.js +494 -0
  22. package/dist/graph-retrieval.d.ts +6 -1
  23. package/dist/graph-revision-reader.d.ts +80 -0
  24. package/dist/graph-revision-reader.js +246 -0
  25. package/dist/graph-web-http.d.ts +26 -0
  26. package/dist/graph-web-http.js +215 -0
  27. package/dist/graph-web.d.ts +364 -0
  28. package/dist/graph-web.js +639 -0
  29. package/dist/judgment-problem-snapshot.d.ts +30 -2
  30. package/dist/judgment-problem-snapshot.js +302 -6
  31. package/dist/judgment-value-proof-review.d.ts +166 -0
  32. package/dist/judgment-value-proof-review.js +437 -0
  33. package/dist/judgment-value-proof.d.ts +28 -1
  34. package/dist/judgment-value-proof.js +53 -8
  35. package/dist/knowledge-continuation.d.ts +127 -0
  36. package/dist/knowledge-continuation.js +446 -0
  37. package/dist/knowledge-lookup.d.ts +178 -0
  38. package/dist/knowledge-lookup.js +758 -0
  39. package/dist/local-web-fetch-bridge.d.ts +5 -0
  40. package/dist/local-web-fetch-bridge.js +37 -0
  41. package/dist/local-web-host.d.ts +109 -0
  42. package/dist/local-web-host.js +688 -0
  43. package/dist/local-web-security.d.ts +25 -0
  44. package/dist/local-web-security.js +95 -0
  45. package/dist/organization-graph.d.ts +15 -0
  46. package/dist/organization-graph.js +24 -4
  47. package/dist/personal-knowledge.d.ts +12 -0
  48. package/dist/personal-knowledge.js +29 -0
  49. package/dist/personal-memory-handover.d.ts +108 -0
  50. package/dist/personal-memory-handover.js +369 -0
  51. package/dist/philosophy-revision-reader.d.ts +85 -0
  52. package/dist/philosophy-revision-reader.js +241 -0
  53. package/dist/portable-graph.d.ts +24 -0
  54. package/dist/portable-graph.js +77 -0
  55. package/dist/server.d.ts +7 -2
  56. package/dist/server.js +9 -6
  57. package/dist/value-proof-review-http.d.ts +25 -0
  58. package/dist/value-proof-review-http.js +185 -0
  59. package/dist/world-model.d.ts +21 -3
  60. package/dist/world-model.js +94 -12
  61. package/package.json +94 -6
  62. package/ui/brainbase-tokens.css +56 -0
  63. package/ui/graph-own-share.js +367 -0
  64. package/ui/graph-projects-view.css +23 -0
  65. package/ui/graph-projects-view.js +730 -0
  66. package/ui/graph-registry-view.css +101 -0
  67. package/ui/graph-registry-view.js +804 -0
  68. package/ui/graph-view-shared.css +522 -0
  69. package/ui/graph-view-shared.js +1227 -0
  70. package/ui/judgment-view.css +119 -45
  71. package/ui/local-web-shell.css +396 -0
  72. package/ui/local-web-shell.js +378 -0
  73. package/ui/objective-editor-http-port.js +154 -0
  74. package/ui/objective-editor.css +180 -74
  75. package/ui/objective-editor.js +490 -43
  76. package/ui/outcome-knowledge.css +50 -14
  77. package/ui/outcome-knowledge.js +15 -1
  78. package/ui/value-proof-review.css +504 -0
  79. package/ui/value-proof-review.js +1104 -0
  80. package/ui/workspace-kit.css +485 -0
  81. package/ui/workspace-kit.js +229 -0
  82. package/ui/world-model-view.css +188 -0
  83. package/ui/world-model-view.js +486 -0
  84. package/ui/icons/mana/LICENSE.tabler.txt +0 -21
  85. package/ui/icons/mana/affiliate.svg +0 -20
  86. package/ui/icons/mana/brand-github.svg +0 -15
  87. package/ui/icons/mana/brand-google-drive.svg +0 -17
  88. package/ui/icons/mana/brand-slack.svg +0 -18
  89. package/ui/icons/mana/cpu.svg +0 -24
  90. package/ui/outcome-mana.css +0 -255
  91. package/ui/outcome-mana.js +0 -2325
@@ -0,0 +1,241 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { isActiveAt } from './canonical-graph.js';
3
+ import { canonicalPortableJson } from './portable-graph.js';
4
+ /** The host owns the canonical philosophy source; this package owns only the read port. */
5
+ export const PHILOSOPHY_REVISION_READ_CONTRACT_VERSION = 'philosophy-revision-read.v1';
6
+ const REVISION_PATTERN = /^[1-9]\d*$/u;
7
+ const DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/u;
8
+ const SCOPE_TYPES = ['personal', 'project', 'organization'];
9
+ const ACL_VISIBILITIES = ['private', 'project', 'organization', 'public'];
10
+ /**
11
+ * Computes the stable identity of a canonical philosophy revision.
12
+ * Current ACL and scope are intentionally excluded so authorization changes do
13
+ * not create a false content revision; they are checked on every read.
14
+ */
15
+ export function philosophyRevisionDigest(input) {
16
+ assertPhilosophyRevisionIdentity(input);
17
+ assertPhilosophyRevisionPayload(input.payload);
18
+ assertPhilosophyRevisionApplicability(input.applicability);
19
+ const content = {
20
+ kind: input.kind,
21
+ id: input.id,
22
+ revision: input.revision,
23
+ payload: input.payload,
24
+ applicability: canonicalApplicability(input.applicability)
25
+ };
26
+ return `sha256:${createHash('sha256').update(canonicalPortableJson(content), 'utf8').digest('hex')}`;
27
+ }
28
+ /** Structural check for callers that need to validate a reference before I/O. */
29
+ export function isPhilosophyRevisionReference(value) {
30
+ if (!isPlainRecord(value) || value.kind !== 'philosophy')
31
+ return false;
32
+ if (!isNonEmptyString(value.id) || !isPositiveRevision(value.revision) || !isDigest(value.digest))
33
+ return false;
34
+ if (!isPhilosophyRevisionScope(value.scope) || !isRfc3339(value.valid_from))
35
+ return false;
36
+ if (value.valid_to === undefined || value.valid_to === null)
37
+ return true;
38
+ return typeof value.valid_to === 'string' && isValidInterval(value.valid_from, value.valid_to);
39
+ }
40
+ /**
41
+ * Adapt canonical philosophy reads to `JudgmentProblemReferenceProvider.resolveOther`.
42
+ * Unknown reference kinds remain unresolved so the caller can compose other
43
+ * providers explicitly.
44
+ */
45
+ export function createJudgmentProblemPhilosophyReferenceResolver(options) {
46
+ if (!options || !options.reader || typeof options.reader.read !== 'function') {
47
+ throw new TypeError('A PhilosophyRevisionReader is required');
48
+ }
49
+ return async (input) => {
50
+ if (input.reference.kind !== 'philosophy') {
51
+ return { status: 'unresolved', message: `Philosophy revision resolver does not handle ${input.reference.kind}` };
52
+ }
53
+ if (!isNonEmptyString(input.context.principal)) {
54
+ return { status: 'unauthorized', message: 'A trusted principal is required for philosophy revision reads' };
55
+ }
56
+ const reference = {
57
+ kind: 'philosophy',
58
+ id: input.reference.id,
59
+ revision: input.reference.revision,
60
+ digest: input.reference.digest,
61
+ scope: cloneScope(input.reference.scope),
62
+ valid_from: input.reference.valid_from,
63
+ ...(input.reference.valid_to == null ? {} : { valid_to: input.reference.valid_to })
64
+ };
65
+ if (!isPhilosophyRevisionReference(reference)) {
66
+ return { status: 'unresolved', message: 'Philosophy revision reference is invalid' };
67
+ }
68
+ let result;
69
+ try {
70
+ result = await options.reader.read({
71
+ reference,
72
+ phase: input.phase,
73
+ // Snapshot access has no caller-supplied mutable scope. The reference
74
+ // scope is the requested boundary; the host must bind it to its trusted
75
+ // current tenant/project context before returning a record.
76
+ context: { principal: input.context.principal, scope: cloneScope(reference.scope) }
77
+ });
78
+ }
79
+ catch {
80
+ return { status: 'unresolved', message: 'Philosophy revision reader failed closed' };
81
+ }
82
+ if (!isPlainRecord(result) || typeof result.status !== 'string') {
83
+ return { status: 'unresolved', message: 'Philosophy revision reader returned an invalid result' };
84
+ }
85
+ if (result.status === 'missing') {
86
+ return { status: 'missing', message: result.message ?? 'Philosophy revision was not found' };
87
+ }
88
+ if (result.status === 'unauthorized') {
89
+ return { status: 'unauthorized', message: result.message ?? 'Philosophy revision is not readable' };
90
+ }
91
+ if (result.status === 'corrupt') {
92
+ return { status: 'unresolved', message: result.message ?? 'Philosophy revision failed integrity validation' };
93
+ }
94
+ if (result.status !== 'resolved' || !isPlainRecord(result.record)) {
95
+ return { status: 'unresolved', message: 'Philosophy revision reader returned an invalid resolved record' };
96
+ }
97
+ const integrity = validateResolvedRecord(result.record, reference, input.context.principal, input.phase);
98
+ if (integrity.status !== 'resolved')
99
+ return integrity;
100
+ return { status: 'resolved', digest: reference.digest };
101
+ };
102
+ }
103
+ function validateResolvedRecord(record, reference, principal, phase) {
104
+ if (record.kind !== 'philosophy' || record.id !== reference.id || record.revision !== reference.revision) {
105
+ return { status: 'unresolved', message: 'Philosophy revision identity does not match the requested reference' };
106
+ }
107
+ if (!isDigest(record.digest) || record.digest !== reference.digest) {
108
+ return { status: 'unresolved', message: 'Philosophy revision digest does not match the requested reference' };
109
+ }
110
+ try {
111
+ assertPhilosophyRevisionPayload(record.payload);
112
+ assertPhilosophyRevisionApplicability(record.applicability);
113
+ if (philosophyRevisionDigest(record) !== record.digest) {
114
+ return { status: 'unresolved', message: 'Philosophy revision payload digest is corrupt' };
115
+ }
116
+ }
117
+ catch {
118
+ return { status: 'unresolved', message: 'Philosophy revision payload or applicability is corrupt' };
119
+ }
120
+ if (!isPhilosophyRevisionAcl(record.currentAcl) || !isPhilosophyRevisionScope(record.currentScope)) {
121
+ return { status: 'unresolved', message: 'Philosophy revision authorization metadata is corrupt' };
122
+ }
123
+ if (!sameScope(record.currentScope, reference.scope)) {
124
+ return { status: 'unauthorized', message: 'Philosophy revision is outside the requested tenant/project scope' };
125
+ }
126
+ // A current save/read must prove that the revision applies to this
127
+ // judgment. Historical reads preserve the original reference and only
128
+ // re-check immutable content plus the current ACL/scope, matching the
129
+ // snapshot provider contract; applicability is not re-evaluated against a
130
+ // later policy boundary.
131
+ if (phase !== 'historical_read') {
132
+ if (!sameScope(record.applicability.scope, reference.scope)) {
133
+ return { status: 'not_applicable', message: 'Philosophy revision is outside the requested applicability scope' };
134
+ }
135
+ const referenceApplicability = referenceApplicabilityFrom(reference);
136
+ if (!periodContains(record.applicability, referenceApplicability)) {
137
+ return { status: 'not_applicable', message: 'Philosophy revision does not cover the requested applicability period' };
138
+ }
139
+ }
140
+ if (!canReadPhilosophyRevision(record.currentAcl, principal)) {
141
+ return { status: 'unauthorized', message: 'Philosophy revision is not readable by the current principal' };
142
+ }
143
+ return { status: 'resolved', digest: reference.digest };
144
+ }
145
+ function assertPhilosophyRevisionIdentity(value) {
146
+ if (value.kind !== 'philosophy' || !isNonEmptyString(value.id) || !isPositiveRevision(value.revision)) {
147
+ throw new TypeError('Philosophy revision identity requires kind, id, and a positive revision');
148
+ }
149
+ }
150
+ function assertPhilosophyRevisionPayload(value) {
151
+ try {
152
+ // canonicalPortableJson rejects undefined, non-JSON objects, non-finite
153
+ // numbers, and cyclic values while preserving opaque philosophy shape.
154
+ canonicalPortableJson(value);
155
+ }
156
+ catch {
157
+ throw new TypeError('Philosophy revision payload must be a JSON value');
158
+ }
159
+ }
160
+ function assertPhilosophyRevisionApplicability(value) {
161
+ if (!isPlainRecord(value) || !isPhilosophyRevisionScope(value.scope) || !isRfc3339(value.validFrom) ||
162
+ (value.validUntil !== undefined && !isRfc3339(value.validUntil)) ||
163
+ (typeof value.validUntil === 'string' && Date.parse(value.validFrom) > Date.parse(value.validUntil))) {
164
+ throw new TypeError('Philosophy revision applicability is invalid');
165
+ }
166
+ }
167
+ function referenceApplicabilityFrom(reference) {
168
+ return {
169
+ scope: cloneScope(reference.scope),
170
+ validFrom: reference.valid_from,
171
+ ...(reference.valid_to == null ? {} : { validUntil: reference.valid_to })
172
+ };
173
+ }
174
+ function canonicalApplicability(value) {
175
+ return {
176
+ scope: cloneScope(value.scope),
177
+ validFrom: value.validFrom,
178
+ ...(value.validUntil === undefined ? {} : { validUntil: value.validUntil })
179
+ };
180
+ }
181
+ function periodContains(source, requested) {
182
+ const sourceFrom = Date.parse(source.validFrom);
183
+ const sourceUntil = source.validUntil === undefined ? Number.POSITIVE_INFINITY : Date.parse(source.validUntil);
184
+ const requestedFrom = Date.parse(requested.validFrom);
185
+ const requestedUntil = requested.validUntil === undefined ? Number.POSITIVE_INFINITY : Date.parse(requested.validUntil);
186
+ return sourceFrom <= requestedFrom && sourceUntil >= requestedUntil;
187
+ }
188
+ function isValidInterval(from, to) {
189
+ return isRfc3339(from) && isRfc3339(to) && Date.parse(from) <= Date.parse(to);
190
+ }
191
+ function isPhilosophyRevisionAcl(value) {
192
+ if (!isPlainRecord(value) || !isNonEmptyString(value.ownerId) ||
193
+ !ACL_VISIBILITIES.includes(value.visibility) ||
194
+ !isStringArray(value.readerIds) || !isStringArray(value.writerIds))
195
+ return false;
196
+ return new Set(value.readerIds).size === value.readerIds.length && new Set(value.writerIds).size === value.writerIds.length;
197
+ }
198
+ function canReadPhilosophyRevision(acl, principal) {
199
+ return acl.visibility === 'public' || acl.ownerId === principal ||
200
+ acl.readerIds.includes(principal) || acl.writerIds.includes(principal);
201
+ }
202
+ function isPhilosophyRevisionScope(value) {
203
+ return isPlainRecord(value) && SCOPE_TYPES.includes(value.type) && isNonEmptyString(value.id);
204
+ }
205
+ function sameScope(left, right) {
206
+ return left.type === right.type && left.id === right.id;
207
+ }
208
+ function cloneScope(scope) {
209
+ return { type: scope.type, id: scope.id };
210
+ }
211
+ function isDigest(value) {
212
+ return typeof value === 'string' && DIGEST_PATTERN.test(value);
213
+ }
214
+ function isPositiveRevision(value) {
215
+ return typeof value === 'string' && REVISION_PATTERN.test(value);
216
+ }
217
+ function isNonEmptyString(value) {
218
+ return typeof value === 'string' && value.trim().length > 0 && !value.includes('\0');
219
+ }
220
+ function isPlainRecord(value) {
221
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
222
+ return false;
223
+ const prototype = Object.getPrototypeOf(value);
224
+ return prototype === Object.prototype || prototype === null;
225
+ }
226
+ function isStringArray(value) {
227
+ return Array.isArray(value) && value.every((item) => isNonEmptyString(item));
228
+ }
229
+ function isRfc3339(value) {
230
+ if (typeof value !== 'string')
231
+ return false;
232
+ try {
233
+ // Reuse the canonical Graph calendar validation so philosophy revision
234
+ // periods reject the same malformed timestamps as Graph records.
235
+ isActiveAt({ validFrom: value }, value);
236
+ return true;
237
+ }
238
+ catch {
239
+ return false;
240
+ }
241
+ }
@@ -29,6 +29,30 @@ export declare function createPortableGraph(os: PersonalOs): PortableGraphBundle
29
29
  export declare function validatePortableGraph(value: unknown): asserts value is PortableGraphBundle;
30
30
  /** Rehydrate a validated bundle into the shared retrieval input shape. */
31
31
  export declare function hydratePortableGraph(bundle: PortableGraphBundle): PersonalOs;
32
+ /** The ID an OSS Personal OS gives its owner; a host replaces it with the owner's own ID when reading a bundle. */
33
+ export declare const PORTABLE_GRAPH_OWNER_PLACEHOLDER = "self";
34
+ export interface PortableGraphOwner {
35
+ /** The owner's person ID in the host that stores the bundle (never `self`). */
36
+ personId: string;
37
+ /** The owner's name in that host, shown in place of the bundle's name for `self`. */
38
+ name?: string | null;
39
+ }
40
+ /**
41
+ * Read a bundle as its owner: every `self` becomes the owner's person ID in
42
+ * the host, and the owner's record carries the host's name for them (the
43
+ * bundle's own name stays as an alias). Replaced: `graph.owner.id`, the
44
+ * entity `self`, relation endpoints, and in the foundation definitions
45
+ * `acl.ownerId` / `readerIds` / `writerIds`, `beneficiaryIds`,
46
+ * `accountableId` and the `subjectIds` of every scope (`scope`,
47
+ * `applicability`, constraint exception scopes).
48
+ *
49
+ * The result is a read projection, not a bundle to store or send: the input
50
+ * is not changed, relation IDs stay those of the stored bundle (they name the
51
+ * relation there) and foundation digests are not recomputed. A bundle that
52
+ * already has another record with the owner's person ID is refused rather
53
+ * than merged.
54
+ */
55
+ export declare function bindPortableGraphOwner(bundle: PortableGraphBundle, owner: PortableGraphOwner): PortableGraphBundle;
32
56
  /** Use exactly the OSS retrieval implementation against a portable bundle. */
33
57
  export declare function retrievePortableGraph(bundle: PortableGraphBundle, input: GraphRetrievalInput, provider?: EmbeddingProvider): Promise<GraphRetrievalResponse>;
34
58
  /** Canonical JSON with recursively sorted object keys and preserved array order. */
@@ -102,6 +102,83 @@ export function hydratePortableGraph(bundle) {
102
102
  sourceCount: 0
103
103
  };
104
104
  }
105
+ /** The ID an OSS Personal OS gives its owner; a host replaces it with the owner's own ID when reading a bundle. */
106
+ export const PORTABLE_GRAPH_OWNER_PLACEHOLDER = 'self';
107
+ /**
108
+ * Read a bundle as its owner: every `self` becomes the owner's person ID in
109
+ * the host, and the owner's record carries the host's name for them (the
110
+ * bundle's own name stays as an alias). Replaced: `graph.owner.id`, the
111
+ * entity `self`, relation endpoints, and in the foundation definitions
112
+ * `acl.ownerId` / `readerIds` / `writerIds`, `beneficiaryIds`,
113
+ * `accountableId` and the `subjectIds` of every scope (`scope`,
114
+ * `applicability`, constraint exception scopes).
115
+ *
116
+ * The result is a read projection, not a bundle to store or send: the input
117
+ * is not changed, relation IDs stay those of the stored bundle (they name the
118
+ * relation there) and foundation digests are not recomputed. A bundle that
119
+ * already has another record with the owner's person ID is refused rather
120
+ * than merged.
121
+ */
122
+ export function bindPortableGraphOwner(bundle, owner) {
123
+ validatePortableGraph(bundle);
124
+ const personId = typeof owner?.personId === 'string' ? owner.personId.trim() : '';
125
+ if (!personId || personId === PORTABLE_GRAPH_OWNER_PLACEHOLDER) {
126
+ throw new Error('PORTABLE-GRAPH-OWNER: the owner person ID is required and cannot be self');
127
+ }
128
+ if (bundle.graph.entities.some((entity) => entity.id === personId)) {
129
+ throw new Error(`PORTABLE-GRAPH-OWNER-CONFLICT: the bundle already has a record ${personId}`);
130
+ }
131
+ const name = typeof owner.name === 'string' && owner.name.trim() ? owner.name.trim() : null;
132
+ const bound = structuredClone(bundle);
133
+ const swap = (id) => (id === PORTABLE_GRAPH_OWNER_PLACEHOLDER ? personId : id);
134
+ const swapAll = (ids) => {
135
+ if (!Array.isArray(ids))
136
+ return;
137
+ for (let index = 0; index < ids.length; index += 1)
138
+ if (typeof ids[index] === 'string')
139
+ ids[index] = swap(ids[index]);
140
+ };
141
+ const graph = bound.graph;
142
+ if (graph.owner) {
143
+ if (graph.owner.id === PORTABLE_GRAPH_OWNER_PLACEHOLDER)
144
+ graph.owner.id = personId;
145
+ if (name)
146
+ graph.owner.name = name;
147
+ }
148
+ for (const entity of graph.entities) {
149
+ if (entity.id !== PORTABLE_GRAPH_OWNER_PLACEHOLDER)
150
+ continue;
151
+ entity.id = personId;
152
+ if (name && name !== entity.name) {
153
+ const aliases = entity.aliases ?? [];
154
+ if (entity.name.trim() && !aliases.includes(entity.name))
155
+ aliases.push(entity.name);
156
+ entity.aliases = aliases;
157
+ entity.name = name;
158
+ }
159
+ }
160
+ for (const edge of graph.edges) {
161
+ edge.fromId = swap(edge.fromId);
162
+ edge.toId = swap(edge.toId);
163
+ }
164
+ for (const record of graph.foundation?.records ?? []) {
165
+ const definition = record.definition;
166
+ if (isPlainRecord(definition.acl)) {
167
+ if (typeof definition.acl.ownerId === 'string')
168
+ definition.acl.ownerId = swap(definition.acl.ownerId);
169
+ swapAll(definition.acl.readerIds);
170
+ swapAll(definition.acl.writerIds);
171
+ }
172
+ swapAll(definition.beneficiaryIds);
173
+ if (typeof definition.accountableId === 'string')
174
+ definition.accountableId = swap(definition.accountableId);
175
+ for (const scope of [definition.scope, definition.applicability, ...(Array.isArray(definition.exceptions) ? definition.exceptions.map((item) => item?.scope) : [])]) {
176
+ if (isPlainRecord(scope))
177
+ swapAll(scope.subjectIds);
178
+ }
179
+ }
180
+ return bound;
181
+ }
105
182
  /** Use exactly the OSS retrieval implementation against a portable bundle. */
106
183
  export function retrievePortableGraph(bundle, input, provider) {
107
184
  return retrieveGraph(hydratePortableGraph(bundle), input, provider);
package/dist/server.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type FoundationPublicConnection } from './foundation-public-provider.js';
1
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
3
  export declare const toolDefinitions: readonly [{
3
4
  readonly name: "get_context";
@@ -634,6 +635,10 @@ export declare const toolDefinitions: readonly [{
634
635
  };
635
636
  };
636
637
  }];
637
- export declare function callBrainbaseTool(name: string, rawArgs?: unknown): Promise<unknown>;
638
- export declare function createServer(): Server;
638
+ export declare function callBrainbaseTool(name: string, rawArgs?: unknown, options?: {
639
+ foundation?: FoundationPublicConnection;
640
+ }): Promise<unknown>;
641
+ export declare function createServer(options?: {
642
+ foundation?: FoundationPublicConnection;
643
+ }): Server;
639
644
  export declare function runMcpServer(): Promise<void>;
package/dist/server.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { callFoundationPublicTool, foundationPublicToolDefinitions } from './foundation-public-provider.js';
1
2
  import { createOrganizationGraphConfig, createOrganizationGraphClient } from './organization-graph.js';
2
3
  import { createHash } from 'node:crypto';
3
4
  import { retrieveGraph } from './graph-retrieval.js';
@@ -447,11 +448,11 @@ function configuredPersonalKnowledgeMode() {
447
448
  }
448
449
  throw new Error(`Unsupported Personal Knowledge mode: ${configuredMode}.`);
449
450
  }
450
- function configuredToolDefinitions() {
451
+ function configuredToolDefinitions(foundation) {
451
452
  if (configuredPersonalKnowledgeMode() === 'managed_cloud') {
452
453
  return toolDefinitions.filter((tool) => isPersonalKnowledgeTool(tool.name));
453
454
  }
454
- return [...toolDefinitions];
455
+ return [...toolDefinitions, ...(foundation ? foundationPublicToolDefinitions : [])];
455
456
  }
456
457
  let embeddingProvider;
457
458
  let embeddingProviderInitialized = false;
@@ -532,11 +533,13 @@ async function callPersonalKnowledgeTool(name, rawArgs) {
532
533
  }
533
534
  throw new Error(`Unknown Personal Knowledge tool: ${name}`);
534
535
  }
535
- export async function callBrainbaseTool(name, rawArgs = {}) {
536
+ export async function callBrainbaseTool(name, rawArgs = {}, options = {}) {
536
537
  const mode = configuredPersonalKnowledgeMode();
537
538
  if (mode === 'managed_cloud' && !isPersonalKnowledgeTool(name)) {
538
539
  throw new Error('managed_cloud Personal Knowledge MCP only permits personal_knowledge_context, personal_knowledge_register, and personal_knowledge_search.');
539
540
  }
541
+ if (name.startsWith('foundation_'))
542
+ return callFoundationPublicTool(name, rawArgs, options.foundation);
540
543
  if (isPersonalKnowledgeTool(name)) {
541
544
  return callPersonalKnowledgeTool(name, rawArgs);
542
545
  }
@@ -857,7 +860,7 @@ function onboardingGuide(state) {
857
860
  };
858
861
  return guides[state];
859
862
  }
860
- export function createServer() {
863
+ export function createServer(options = {}) {
861
864
  const server = new Server({
862
865
  name: 'brainbase-mcp',
863
866
  version: '0.1.0'
@@ -867,10 +870,10 @@ export function createServer() {
867
870
  }
868
871
  });
869
872
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
870
- tools: configuredToolDefinitions()
873
+ tools: configuredToolDefinitions(options.foundation)
871
874
  }));
872
875
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
873
- const result = await callBrainbaseTool(request.params.name, request.params.arguments);
876
+ const result = await callBrainbaseTool(request.params.name, request.params.arguments, options);
874
877
  return {
875
878
  content: [
876
879
  {
@@ -0,0 +1,25 @@
1
+ import { type IncomingMessage, type Server, type ServerResponse } from 'node:http';
2
+ export declare const VALUE_PROOF_REVIEW_HTTP_VERSION: "value-proof-review-http.v1";
3
+ export declare const VALUE_PROOF_REVIEW_TOKEN_HEADER = "x-brainbase-review-token";
4
+ export interface ValueProofReviewHttpOptions {
5
+ /** Judgment journal root. Defaults to `~/.brainbase/personal-os/judgment-journal`. */
6
+ readonly journalRoot?: string;
7
+ /** Personal OS data directory that stores owner feedback. */
8
+ readonly dataDir?: string;
9
+ /** Required on mutating requests. */
10
+ readonly token: string;
11
+ readonly basePath?: string;
12
+ readonly now?: () => Date;
13
+ }
14
+ export type ValueProofReviewHttpHandler = (request: IncomingMessage, response: ServerResponse) => Promise<boolean>;
15
+ export declare function createValueProofReviewHttpHandler(options: ValueProofReviewHttpOptions): ValueProofReviewHttpHandler;
16
+ export interface ValueProofReviewHostOptions extends Omit<ValueProofReviewHttpOptions, 'token'> {
17
+ readonly token?: string;
18
+ /** Directory containing the packaged `ui/` files. */
19
+ readonly uiDir?: string;
20
+ }
21
+ /** Local single-owner host. The caller owns `listen()` and must bind to a loopback address. */
22
+ export declare function createValueProofReviewHost(options?: ValueProofReviewHostOptions): {
23
+ readonly server: Server;
24
+ readonly token: string;
25
+ };
@@ -0,0 +1,185 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { createServer } from 'node:http';
4
+ import { join, relative } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { buildJudgmentValueProofReviewHome, readJudgmentValueProofFeedback, readJudgmentValueProofJournal, recordJudgmentValueProofFeedback } from './judgment-value-proof-review.js';
7
+ import { assertSameOrigin, isLoopbackHost, LOCAL_WEB_CONTENT_SECURITY_POLICY, LocalWebHttpError as HttpError, readJsonObjectBody, requestUrl, tokenMatches, writeHttpError as writeError, writeJson } from './local-web-security.js';
8
+ export const VALUE_PROOF_REVIEW_HTTP_VERSION = 'value-proof-review-http.v1';
9
+ export const VALUE_PROOF_REVIEW_TOKEN_HEADER = 'x-brainbase-review-token';
10
+ const DEFAULT_BASE_PATH = '/api/value-proofs';
11
+ const MAX_BODY_BYTES = 16 * 1024;
12
+ // The review part imports the shared screen pattern (workspace-kit), which reads the shared tokens.
13
+ const UI_FILES = Object.freeze({
14
+ '/ui/brainbase-tokens.css': { file: 'brainbase-tokens.css', type: 'text/css; charset=utf-8' },
15
+ '/ui/workspace-kit.js': { file: 'workspace-kit.js', type: 'text/javascript; charset=utf-8' },
16
+ '/ui/workspace-kit.css': { file: 'workspace-kit.css', type: 'text/css; charset=utf-8' },
17
+ '/ui/value-proof-review.js': { file: 'value-proof-review.js', type: 'text/javascript; charset=utf-8' },
18
+ '/ui/value-proof-review.css': { file: 'value-proof-review.css', type: 'text/css; charset=utf-8' }
19
+ });
20
+ function publicHome(home) {
21
+ if (home.status === 'unavailable')
22
+ return home;
23
+ const strip = (items) => Object.fromEntries(Object.entries(items).map(([section, list]) => [
24
+ section,
25
+ list.map(({ section: itemSection, proof, feedback_history }) => ({ section: itemSection, proof, feedback_history }))
26
+ ]));
27
+ return {
28
+ status: home.status,
29
+ root: home.root,
30
+ coverage: home.coverage,
31
+ sections: strip(home.sections),
32
+ // Holds only decision IDs, counts and states; no local paths.
33
+ delegation_map: home.delegation_map,
34
+ rejected: home.rejected.map((entry) => ({ file: relative(home.root, entry.file), reason: entry.reason }))
35
+ };
36
+ }
37
+ export function createValueProofReviewHttpHandler(options) {
38
+ if (typeof options.token !== 'string' || options.token.length < 16) {
39
+ throw new TypeError('token must be at least 16 characters');
40
+ }
41
+ const basePath = (options.basePath ?? DEFAULT_BASE_PATH).replace(/\/+$/u, '');
42
+ const now = options.now ?? (() => new Date());
43
+ async function readHome() {
44
+ const journal = await readJudgmentValueProofJournal({ root: options.journalRoot });
45
+ const feedback = journal.status === 'available'
46
+ ? await readJudgmentValueProofFeedback({ dataDir: options.dataDir })
47
+ : [];
48
+ return buildJudgmentValueProofReviewHome(journal, feedback, { now: now() });
49
+ }
50
+ async function recordFeedback(request) {
51
+ assertSameOrigin(request);
52
+ if (!tokenMatches(options.token, request.headers[VALUE_PROOF_REVIEW_TOKEN_HEADER])) {
53
+ throw new HttpError(403, 'review_token_required', 'A valid review token is required');
54
+ }
55
+ const body = await readJsonObjectBody(request, MAX_BODY_BYTES);
56
+ const intentId = typeof body.intent_id === 'string' ? body.intent_id : '';
57
+ const attemptId = typeof body.decision_attempt_id === 'string' ? body.decision_attempt_id : '';
58
+ const journal = await readJudgmentValueProofJournal({ root: options.journalRoot });
59
+ if (journal.status === 'unavailable') {
60
+ throw new HttpError(503, 'judgment_journal_unavailable', journal.reason);
61
+ }
62
+ const known = journal.entries.some((entry) => entry.proof.intent_id === intentId
63
+ && entry.proof.decision_attempt_id === attemptId);
64
+ if (!known)
65
+ throw new HttpError(404, 'value_proof_not_found', 'No saved value proof matches this decision');
66
+ try {
67
+ const result = await recordJudgmentValueProofFeedback({
68
+ dataDir: options.dataDir,
69
+ intent_id: intentId,
70
+ decision_attempt_id: attemptId,
71
+ status: body.status,
72
+ summary: typeof body.summary === 'string' ? body.summary : null,
73
+ target_layer: typeof body.target_layer === 'string' ? body.target_layer : null,
74
+ now: now()
75
+ });
76
+ return { statusCode: result.created ? 201 : 200, body: result };
77
+ }
78
+ catch (error) {
79
+ if (error instanceof TypeError)
80
+ throw new HttpError(400, 'invalid_feedback', error.message);
81
+ throw error;
82
+ }
83
+ }
84
+ return async (request, response) => {
85
+ const path = requestUrl(request).pathname;
86
+ if (path !== `${basePath}/home` && path !== `${basePath}/feedback`)
87
+ return false;
88
+ try {
89
+ if (path === `${basePath}/home`) {
90
+ if (request.method !== 'GET')
91
+ throw new HttpError(405, 'method_not_allowed', 'Use GET');
92
+ writeJson(response, 200, publicHome(await readHome()));
93
+ return true;
94
+ }
95
+ if (request.method !== 'POST')
96
+ throw new HttpError(405, 'method_not_allowed', 'Use POST');
97
+ const result = await recordFeedback(request);
98
+ writeJson(response, result.statusCode, result.body);
99
+ }
100
+ catch (error) {
101
+ if (error instanceof HttpError)
102
+ writeError(response, error);
103
+ else
104
+ writeJson(response, 500, { error: { code: 'internal_error', message: 'Unexpected error' } });
105
+ }
106
+ return true;
107
+ };
108
+ }
109
+ function escapeAttribute(value) {
110
+ return value.replace(/[&"<>]/gu, (character) => ({ '&': '&amp;', '"': '&quot;', '<': '&lt;', '>': '&gt;' }[character] ?? character));
111
+ }
112
+ function shellHtml(token, basePath) {
113
+ return `<!doctype html>
114
+ <html lang="ja">
115
+ <head>
116
+ <meta charset="utf-8">
117
+ <meta name="viewport" content="width=device-width, initial-scale=1">
118
+ <meta name="brainbase-review-token" content="${escapeAttribute(token)}">
119
+ <meta name="brainbase-review-base-path" content="${escapeAttribute(basePath)}">
120
+ <title>Brainbase 判断の見返し</title>
121
+ <link rel="stylesheet" href="/ui/brainbase-tokens.css">
122
+ <link rel="stylesheet" href="/ui/workspace-kit.css">
123
+ <link rel="stylesheet" href="/ui/value-proof-review.css">
124
+ </head>
125
+ <body>
126
+ <main id="brainbase-value-proof-review"></main>
127
+ <script type="module" src="/app.js"></script>
128
+ </body>
129
+ </html>
130
+ `;
131
+ }
132
+ const BOOTSTRAP_JS = `import { createValueProofReviewUI } from '/ui/value-proof-review.js';
133
+ const meta = (name) => document.querySelector(\`meta[name="\${name}"]\`)?.getAttribute('content') ?? '';
134
+ createValueProofReviewUI({
135
+ root: document.getElementById('brainbase-value-proof-review'),
136
+ basePath: meta('brainbase-review-base-path'),
137
+ token: meta('brainbase-review-token')
138
+ });
139
+ `;
140
+ /** Local single-owner host. The caller owns `listen()` and must bind to a loopback address. */
141
+ export function createValueProofReviewHost(options = {}) {
142
+ const token = options.token ?? randomBytes(24).toString('base64url');
143
+ const basePath = (options.basePath ?? DEFAULT_BASE_PATH).replace(/\/+$/u, '');
144
+ const uiDir = options.uiDir ?? fileURLToPath(new URL('../ui/', import.meta.url));
145
+ const handler = createValueProofReviewHttpHandler({ ...options, token, basePath });
146
+ const server = createServer((request, response) => {
147
+ void (async () => {
148
+ if (!isLoopbackHost(request)) {
149
+ writeJson(response, 403, { error: { code: 'host_rejected', message: 'Host must be a loopback address for this server' } });
150
+ return;
151
+ }
152
+ if (await handler(request, response))
153
+ return;
154
+ const path = requestUrl(request).pathname;
155
+ if (request.method !== 'GET') {
156
+ writeJson(response, 405, { error: { code: 'method_not_allowed', message: 'Use GET' } });
157
+ return;
158
+ }
159
+ response.setHeader('Cache-Control', 'no-store');
160
+ response.setHeader('X-Content-Type-Options', 'nosniff');
161
+ if (path === '/') {
162
+ response.setHeader('Content-Type', 'text/html; charset=utf-8');
163
+ response.setHeader('Content-Security-Policy', LOCAL_WEB_CONTENT_SECURITY_POLICY);
164
+ response.setHeader('Referrer-Policy', 'no-referrer');
165
+ response.end(shellHtml(token, basePath));
166
+ return;
167
+ }
168
+ if (path === '/app.js') {
169
+ response.setHeader('Content-Type', 'text/javascript; charset=utf-8');
170
+ response.end(BOOTSTRAP_JS);
171
+ return;
172
+ }
173
+ const asset = UI_FILES[path];
174
+ if (!asset) {
175
+ writeJson(response, 404, { error: { code: 'not_found', message: 'Not found' } });
176
+ return;
177
+ }
178
+ response.setHeader('Content-Type', asset.type);
179
+ response.end(await readFile(join(uiDir, asset.file)));
180
+ })().catch(() => {
181
+ writeJson(response, 500, { error: { code: 'internal_error', message: 'Unexpected error' } });
182
+ });
183
+ });
184
+ return { server, token };
185
+ }