ont-run 0.0.1 → 0.0.2
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 +2 -0
- package/dist/bin/ont.js +99 -12
- package/dist/index.js +167 -22
- package/dist/src/browser/transform.d.ts +1 -0
- package/dist/src/config/categorical.d.ts +59 -0
- package/dist/src/config/index.d.ts +3 -3
- package/dist/src/config/schema.d.ts +9 -0
- package/dist/src/config/types.d.ts +14 -2
- package/dist/src/index.d.ts +2 -2
- package/dist/src/lockfile/types.d.ts +4 -0
- package/dist/src/server/api/middleware.d.ts +7 -1
- package/dist/src/server/mcp/tools.d.ts +3 -3
- package/package.json +2 -2
- package/src/browser/server.ts +27 -1
- package/src/browser/transform.ts +7 -1
- package/src/cli/commands/init.ts +38 -6
- package/src/config/categorical.ts +90 -0
- package/src/config/index.ts +3 -1
- package/src/config/schema.ts +64 -1
- package/src/config/types.ts +15 -2
- package/src/index.ts +2 -1
- package/src/lockfile/differ.ts +9 -1
- package/src/lockfile/hasher.ts +6 -1
- package/src/lockfile/types.ts +4 -0
- package/src/server/api/middleware.ts +18 -4
- package/src/server/api/router.ts +26 -3
- package/src/server/mcp/index.ts +31 -12
- package/src/server/mcp/tools.ts +67 -6
- package/src/server/start.ts +9 -0
package/README.md
CHANGED
|
@@ -21,12 +21,14 @@ export default defineOntology({
|
|
|
21
21
|
getTicket: {
|
|
22
22
|
description: 'Get ticket details',
|
|
23
23
|
access: ['support', 'admin'],
|
|
24
|
+
entities: ['Ticket'],
|
|
24
25
|
inputs: z.object({ ticketId: z.string().uuid() }),
|
|
25
26
|
resolver: './resolvers/getTicket.ts',
|
|
26
27
|
},
|
|
27
28
|
assignTicket: {
|
|
28
29
|
description: 'Assign ticket to an agent',
|
|
29
30
|
access: ['admin'], // If AI tries to add 'public' here, review is triggered
|
|
31
|
+
entities: ['Ticket'],
|
|
30
32
|
inputs: z.object({ ticketId: z.string().uuid(), assignee: z.string() }),
|
|
31
33
|
resolver: './resolvers/assignTicket.ts',
|
|
32
34
|
},
|
package/dist/bin/ont.js
CHANGED
|
@@ -6165,9 +6165,26 @@ function getFieldFromMetadata(schema) {
|
|
|
6165
6165
|
}
|
|
6166
6166
|
return null;
|
|
6167
6167
|
}
|
|
6168
|
-
|
|
6168
|
+
function hasUserContextMetadata(schema) {
|
|
6169
|
+
return schema !== null && typeof schema === "object" && USER_CONTEXT_METADATA in schema && schema[USER_CONTEXT_METADATA] === true;
|
|
6170
|
+
}
|
|
6171
|
+
function getUserContextFields(schema) {
|
|
6172
|
+
const fields = [];
|
|
6173
|
+
if (schema instanceof exports_external.ZodObject) {
|
|
6174
|
+
const shape = schema.shape;
|
|
6175
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
6176
|
+
if (hasUserContextMetadata(value)) {
|
|
6177
|
+
fields.push(key);
|
|
6178
|
+
}
|
|
6179
|
+
}
|
|
6180
|
+
}
|
|
6181
|
+
return fields;
|
|
6182
|
+
}
|
|
6183
|
+
var FIELD_FROM_METADATA, USER_CONTEXT_METADATA;
|
|
6169
6184
|
var init_categorical = __esm(() => {
|
|
6185
|
+
init_zod();
|
|
6170
6186
|
FIELD_FROM_METADATA = Symbol.for("ont:fieldFrom");
|
|
6187
|
+
USER_CONTEXT_METADATA = Symbol.for("ont:userContext");
|
|
6171
6188
|
});
|
|
6172
6189
|
|
|
6173
6190
|
// src/lockfile/hasher.ts
|
|
@@ -6226,13 +6243,16 @@ function extractOntology(config) {
|
|
|
6226
6243
|
}
|
|
6227
6244
|
}
|
|
6228
6245
|
const fieldReferences = extractFieldReferences(fn.inputs);
|
|
6246
|
+
const userContextFields = getUserContextFields(fn.inputs);
|
|
6247
|
+
const usesUserContext = userContextFields.length > 0;
|
|
6229
6248
|
functions[name] = {
|
|
6230
6249
|
description: fn.description,
|
|
6231
6250
|
access: [...fn.access].sort(),
|
|
6232
6251
|
entities: [...fn.entities].sort(),
|
|
6233
6252
|
inputsSchema,
|
|
6234
6253
|
outputsSchema,
|
|
6235
|
-
fieldReferences: fieldReferences.length > 0 ? fieldReferences : undefined
|
|
6254
|
+
fieldReferences: fieldReferences.length > 0 ? fieldReferences : undefined,
|
|
6255
|
+
usesUserContext: usesUserContext || undefined
|
|
6236
6256
|
};
|
|
6237
6257
|
}
|
|
6238
6258
|
return {
|
|
@@ -6330,7 +6350,8 @@ function diffOntology(oldOntology, newOntology) {
|
|
|
6330
6350
|
const outputsChanged = JSON.stringify(oldFn.outputsSchema) !== JSON.stringify(newFn.outputsSchema);
|
|
6331
6351
|
const entitiesChanged = JSON.stringify(oldFn.entities) !== JSON.stringify(newFn.entities);
|
|
6332
6352
|
const fieldReferencesChanged = JSON.stringify(oldFn.fieldReferences) !== JSON.stringify(newFn.fieldReferences);
|
|
6333
|
-
|
|
6353
|
+
const userContextChanged = !!oldFn.usesUserContext !== !!newFn.usesUserContext;
|
|
6354
|
+
if (accessChanged || descriptionChanged || inputsChanged || outputsChanged || entitiesChanged || fieldReferencesChanged || userContextChanged) {
|
|
6334
6355
|
functions.push({
|
|
6335
6356
|
name,
|
|
6336
6357
|
type: "modified",
|
|
@@ -6343,7 +6364,9 @@ function diffOntology(oldOntology, newOntology) {
|
|
|
6343
6364
|
entitiesChanged: entitiesChanged || undefined,
|
|
6344
6365
|
oldEntities: entitiesChanged ? oldFn.entities : undefined,
|
|
6345
6366
|
newEntities: entitiesChanged ? newFn.entities : undefined,
|
|
6346
|
-
fieldReferencesChanged: fieldReferencesChanged || undefined
|
|
6367
|
+
fieldReferencesChanged: fieldReferencesChanged || undefined,
|
|
6368
|
+
userContextChanged: userContextChanged || undefined,
|
|
6369
|
+
usesUserContext: userContextChanged ? newFn.usesUserContext : undefined
|
|
6347
6370
|
});
|
|
6348
6371
|
}
|
|
6349
6372
|
}
|
|
@@ -6421,6 +6444,9 @@ function formatDiffForConsole(diff) {
|
|
|
6421
6444
|
if (fn.fieldReferencesChanged) {
|
|
6422
6445
|
lines.push(` Field references: changed`);
|
|
6423
6446
|
}
|
|
6447
|
+
if (fn.userContextChanged) {
|
|
6448
|
+
lines.push(` User context: ${fn.usesUserContext ? "added" : "removed"}`);
|
|
6449
|
+
}
|
|
6424
6450
|
}
|
|
6425
6451
|
}
|
|
6426
6452
|
}
|
|
@@ -8405,7 +8431,7 @@ var initCommand = defineCommand({
|
|
|
8405
8431
|
if (!existsSync(resolversDir)) {
|
|
8406
8432
|
mkdirSync(resolversDir, { recursive: true });
|
|
8407
8433
|
}
|
|
8408
|
-
const configTemplate = `import { defineOntology } from 'ont-run';
|
|
8434
|
+
const configTemplate = `import { defineOntology, userContext } from 'ont-run';
|
|
8409
8435
|
import { z } from 'zod';
|
|
8410
8436
|
|
|
8411
8437
|
export default defineOntology({
|
|
@@ -8417,13 +8443,22 @@ export default defineOntology({
|
|
|
8417
8443
|
},
|
|
8418
8444
|
|
|
8419
8445
|
// Pluggable auth - customize this for your use case
|
|
8446
|
+
// Return { groups, user } for row-level access control
|
|
8420
8447
|
auth: async (req) => {
|
|
8421
8448
|
const token = req.headers.get('Authorization');
|
|
8422
|
-
// Return access groups
|
|
8449
|
+
// Return access groups and optional user data
|
|
8423
8450
|
// This is where you'd verify JWTs, API keys, etc.
|
|
8424
|
-
if (!token) return ['public'];
|
|
8425
|
-
if (token === 'admin-secret')
|
|
8426
|
-
|
|
8451
|
+
if (!token) return { groups: ['public'] };
|
|
8452
|
+
if (token === 'admin-secret') {
|
|
8453
|
+
return {
|
|
8454
|
+
groups: ['admin', 'support', 'public'],
|
|
8455
|
+
user: { id: 'admin-1', email: 'admin@example.com' },
|
|
8456
|
+
};
|
|
8457
|
+
}
|
|
8458
|
+
return {
|
|
8459
|
+
groups: ['support', 'public'],
|
|
8460
|
+
user: { id: 'user-1', email: 'user@example.com' },
|
|
8461
|
+
};
|
|
8427
8462
|
},
|
|
8428
8463
|
|
|
8429
8464
|
accessGroups: {
|
|
@@ -8432,21 +8467,32 @@ export default defineOntology({
|
|
|
8432
8467
|
admin: { description: 'Administrators' },
|
|
8433
8468
|
},
|
|
8434
8469
|
|
|
8470
|
+
entities: {
|
|
8471
|
+
User: { description: 'A user account' },
|
|
8472
|
+
},
|
|
8473
|
+
|
|
8435
8474
|
functions: {
|
|
8436
8475
|
// Example: Public function
|
|
8437
8476
|
healthCheck: {
|
|
8438
8477
|
description: 'Check API health status',
|
|
8439
8478
|
access: ['public', 'support', 'admin'],
|
|
8479
|
+
entities: [],
|
|
8440
8480
|
inputs: z.object({}),
|
|
8441
8481
|
resolver: './resolvers/healthCheck.ts',
|
|
8442
8482
|
},
|
|
8443
8483
|
|
|
8444
|
-
// Example: Restricted function
|
|
8484
|
+
// Example: Restricted function with row-level access
|
|
8445
8485
|
getUser: {
|
|
8446
8486
|
description: 'Get user details by ID',
|
|
8447
8487
|
access: ['support', 'admin'],
|
|
8488
|
+
entities: ['User'],
|
|
8448
8489
|
inputs: z.object({
|
|
8449
8490
|
userId: z.string().uuid(),
|
|
8491
|
+
// currentUser is injected from auth - not visible to API callers
|
|
8492
|
+
currentUser: userContext(z.object({
|
|
8493
|
+
id: z.string(),
|
|
8494
|
+
email: z.string(),
|
|
8495
|
+
})),
|
|
8450
8496
|
}),
|
|
8451
8497
|
resolver: './resolvers/getUser.ts',
|
|
8452
8498
|
},
|
|
@@ -8455,6 +8501,7 @@ export default defineOntology({
|
|
|
8455
8501
|
deleteUser: {
|
|
8456
8502
|
description: 'Delete a user account',
|
|
8457
8503
|
access: ['admin'],
|
|
8504
|
+
entities: ['User'],
|
|
8458
8505
|
inputs: z.object({
|
|
8459
8506
|
userId: z.string().uuid(),
|
|
8460
8507
|
reason: z.string().optional(),
|
|
@@ -8482,10 +8529,21 @@ export default async function healthCheck(ctx: ResolverContext, args: {}) {
|
|
|
8482
8529
|
|
|
8483
8530
|
interface GetUserArgs {
|
|
8484
8531
|
userId: string;
|
|
8532
|
+
currentUser: {
|
|
8533
|
+
id: string;
|
|
8534
|
+
email: string;
|
|
8535
|
+
};
|
|
8485
8536
|
}
|
|
8486
8537
|
|
|
8487
8538
|
export default async function getUser(ctx: ResolverContext, args: GetUserArgs) {
|
|
8488
8539
|
ctx.logger.info(\`Getting user: \${args.userId}\`);
|
|
8540
|
+
ctx.logger.info(\`Requested by: \${args.currentUser.email}\`);
|
|
8541
|
+
|
|
8542
|
+
// Example: Check if user can access this resource
|
|
8543
|
+
// Support can only view their own account
|
|
8544
|
+
if (!ctx.accessGroups.includes('admin') && args.userId !== args.currentUser.id) {
|
|
8545
|
+
throw new Error('You can only view your own account');
|
|
8546
|
+
}
|
|
8489
8547
|
|
|
8490
8548
|
// This is where you'd query your database
|
|
8491
8549
|
// Example response:
|
|
@@ -10786,6 +10844,8 @@ function transformToGraphData(config) {
|
|
|
10786
10844
|
for (const entity of fn.entities) {
|
|
10787
10845
|
entityCounts[entity] = (entityCounts[entity] || 0) + 1;
|
|
10788
10846
|
}
|
|
10847
|
+
const userContextFields = getUserContextFields(fn.inputs);
|
|
10848
|
+
const usesUserContext = userContextFields.length > 0;
|
|
10789
10849
|
nodes.push({
|
|
10790
10850
|
id: `function:${name}`,
|
|
10791
10851
|
type: "function",
|
|
@@ -10794,7 +10854,8 @@ function transformToGraphData(config) {
|
|
|
10794
10854
|
metadata: {
|
|
10795
10855
|
inputs: safeZodToJsonSchema(fn.inputs),
|
|
10796
10856
|
outputs: fn.outputs ? safeZodToJsonSchema(fn.outputs) : undefined,
|
|
10797
|
-
resolver: fn.resolver
|
|
10857
|
+
resolver: fn.resolver,
|
|
10858
|
+
usesUserContext: usesUserContext || undefined
|
|
10798
10859
|
}
|
|
10799
10860
|
});
|
|
10800
10861
|
for (const group of fn.access) {
|
|
@@ -11386,6 +11447,27 @@ function generateBrowserUI(graphData) {
|
|
|
11386
11447
|
color: var(--change-modified);
|
|
11387
11448
|
}
|
|
11388
11449
|
|
|
11450
|
+
/* User Context Badge */
|
|
11451
|
+
.user-context-badge {
|
|
11452
|
+
display: inline-flex;
|
|
11453
|
+
align-items: center;
|
|
11454
|
+
gap: 4px;
|
|
11455
|
+
padding: 2px 8px;
|
|
11456
|
+
border-radius: 9999px;
|
|
11457
|
+
font-size: 10px;
|
|
11458
|
+
font-weight: 500;
|
|
11459
|
+
background: rgba(21, 168, 168, 0.12);
|
|
11460
|
+
color: var(--vanna-teal);
|
|
11461
|
+
text-transform: uppercase;
|
|
11462
|
+
letter-spacing: 0.03em;
|
|
11463
|
+
margin-left: 8px;
|
|
11464
|
+
}
|
|
11465
|
+
|
|
11466
|
+
.user-context-badge svg {
|
|
11467
|
+
width: 12px;
|
|
11468
|
+
height: 12px;
|
|
11469
|
+
}
|
|
11470
|
+
|
|
11389
11471
|
/* Review Footer */
|
|
11390
11472
|
.review-footer {
|
|
11391
11473
|
position: fixed;
|
|
@@ -12854,9 +12936,14 @@ function generateBrowserUI(graphData) {
|
|
|
12854
12936
|
? \`<span class="detail-change-badge \${changeStatus}">\${changeStatus === 'added' ? 'New' : changeStatus === 'removed' ? 'Removed' : 'Modified'}</span>\`
|
|
12855
12937
|
: '';
|
|
12856
12938
|
|
|
12939
|
+
// Build user context badge if applicable
|
|
12940
|
+
const userContextBadge = data.metadata?.usesUserContext
|
|
12941
|
+
? \`<span class="user-context-badge"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>User Context</span>\`
|
|
12942
|
+
: '';
|
|
12943
|
+
|
|
12857
12944
|
let html = \`
|
|
12858
12945
|
<div class="detail-header">
|
|
12859
|
-
<div class="detail-type \${data.type}">\${formatType(data.type)}\${changeBadge}</div>
|
|
12946
|
+
<div class="detail-type \${data.type}">\${formatType(data.type)}\${changeBadge}\${userContextBadge}</div>
|
|
12860
12947
|
<div class="detail-name">\${data.label}</div>
|
|
12861
12948
|
<div class="detail-description">\${data.description || 'No description'}</div>
|
|
12862
12949
|
</div>
|
package/dist/index.js
CHANGED
|
@@ -4007,10 +4007,31 @@ function getFieldFromMetadata(schema) {
|
|
|
4007
4007
|
}
|
|
4008
4008
|
return null;
|
|
4009
4009
|
}
|
|
4010
|
-
|
|
4010
|
+
function userContext(schema) {
|
|
4011
|
+
const marked = schema;
|
|
4012
|
+
marked[USER_CONTEXT_METADATA] = true;
|
|
4013
|
+
return marked;
|
|
4014
|
+
}
|
|
4015
|
+
function hasUserContextMetadata(schema) {
|
|
4016
|
+
return schema !== null && typeof schema === "object" && USER_CONTEXT_METADATA in schema && schema[USER_CONTEXT_METADATA] === true;
|
|
4017
|
+
}
|
|
4018
|
+
function getUserContextFields(schema) {
|
|
4019
|
+
const fields = [];
|
|
4020
|
+
if (schema instanceof exports_external.ZodObject) {
|
|
4021
|
+
const shape = schema.shape;
|
|
4022
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
4023
|
+
if (hasUserContextMetadata(value)) {
|
|
4024
|
+
fields.push(key);
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
return fields;
|
|
4029
|
+
}
|
|
4030
|
+
var FIELD_FROM_METADATA, USER_CONTEXT_METADATA;
|
|
4011
4031
|
var init_categorical = __esm(() => {
|
|
4012
4032
|
init_zod();
|
|
4013
4033
|
FIELD_FROM_METADATA = Symbol.for("ont:fieldFrom");
|
|
4034
|
+
USER_CONTEXT_METADATA = Symbol.for("ont:userContext");
|
|
4014
4035
|
});
|
|
4015
4036
|
|
|
4016
4037
|
// node_modules/consola/dist/chunks/prompt.mjs
|
|
@@ -6229,13 +6250,16 @@ function extractOntology(config) {
|
|
|
6229
6250
|
}
|
|
6230
6251
|
}
|
|
6231
6252
|
const fieldReferences = extractFieldReferences(fn.inputs);
|
|
6253
|
+
const userContextFields = getUserContextFields(fn.inputs);
|
|
6254
|
+
const usesUserContext = userContextFields.length > 0;
|
|
6232
6255
|
functions[name] = {
|
|
6233
6256
|
description: fn.description,
|
|
6234
6257
|
access: [...fn.access].sort(),
|
|
6235
6258
|
entities: [...fn.entities].sort(),
|
|
6236
6259
|
inputsSchema,
|
|
6237
6260
|
outputsSchema,
|
|
6238
|
-
fieldReferences: fieldReferences.length > 0 ? fieldReferences : undefined
|
|
6261
|
+
fieldReferences: fieldReferences.length > 0 ? fieldReferences : undefined,
|
|
6262
|
+
usesUserContext: usesUserContext || undefined
|
|
6239
6263
|
};
|
|
6240
6264
|
}
|
|
6241
6265
|
return {
|
|
@@ -6333,7 +6357,8 @@ function diffOntology(oldOntology, newOntology) {
|
|
|
6333
6357
|
const outputsChanged = JSON.stringify(oldFn.outputsSchema) !== JSON.stringify(newFn.outputsSchema);
|
|
6334
6358
|
const entitiesChanged = JSON.stringify(oldFn.entities) !== JSON.stringify(newFn.entities);
|
|
6335
6359
|
const fieldReferencesChanged = JSON.stringify(oldFn.fieldReferences) !== JSON.stringify(newFn.fieldReferences);
|
|
6336
|
-
|
|
6360
|
+
const userContextChanged = !!oldFn.usesUserContext !== !!newFn.usesUserContext;
|
|
6361
|
+
if (accessChanged || descriptionChanged || inputsChanged || outputsChanged || entitiesChanged || fieldReferencesChanged || userContextChanged) {
|
|
6337
6362
|
functions.push({
|
|
6338
6363
|
name,
|
|
6339
6364
|
type: "modified",
|
|
@@ -6346,7 +6371,9 @@ function diffOntology(oldOntology, newOntology) {
|
|
|
6346
6371
|
entitiesChanged: entitiesChanged || undefined,
|
|
6347
6372
|
oldEntities: entitiesChanged ? oldFn.entities : undefined,
|
|
6348
6373
|
newEntities: entitiesChanged ? newFn.entities : undefined,
|
|
6349
|
-
fieldReferencesChanged: fieldReferencesChanged || undefined
|
|
6374
|
+
fieldReferencesChanged: fieldReferencesChanged || undefined,
|
|
6375
|
+
userContextChanged: userContextChanged || undefined,
|
|
6376
|
+
usesUserContext: userContextChanged ? newFn.usesUserContext : undefined
|
|
6350
6377
|
});
|
|
6351
6378
|
}
|
|
6352
6379
|
}
|
|
@@ -6424,6 +6451,9 @@ function formatDiffForConsole(diff) {
|
|
|
6424
6451
|
if (fn.fieldReferencesChanged) {
|
|
6425
6452
|
lines.push(` Field references: changed`);
|
|
6426
6453
|
}
|
|
6454
|
+
if (fn.userContextChanged) {
|
|
6455
|
+
lines.push(` User context: ${fn.usesUserContext ? "added" : "removed"}`);
|
|
6456
|
+
}
|
|
6427
6457
|
}
|
|
6428
6458
|
}
|
|
6429
6459
|
}
|
|
@@ -13531,6 +13561,41 @@ function validateFieldFromReferences(config) {
|
|
|
13531
13561
|
}
|
|
13532
13562
|
}
|
|
13533
13563
|
}
|
|
13564
|
+
async function validateUserContextRequirements(config) {
|
|
13565
|
+
const functionsWithUserContext = [];
|
|
13566
|
+
for (const [fnName, fn] of Object.entries(config.functions)) {
|
|
13567
|
+
const userContextFields = getUserContextFields(fn.inputs);
|
|
13568
|
+
if (userContextFields.length > 0) {
|
|
13569
|
+
functionsWithUserContext.push(fnName);
|
|
13570
|
+
}
|
|
13571
|
+
}
|
|
13572
|
+
if (functionsWithUserContext.length === 0) {
|
|
13573
|
+
return;
|
|
13574
|
+
}
|
|
13575
|
+
const mockRequest = new Request("http://localhost/test", {
|
|
13576
|
+
headers: { Authorization: "test-token" }
|
|
13577
|
+
});
|
|
13578
|
+
try {
|
|
13579
|
+
const authResult = await config.auth(mockRequest);
|
|
13580
|
+
const hasUserField = authResult !== null && typeof authResult === "object" && !Array.isArray(authResult) && "user" in authResult;
|
|
13581
|
+
if (!hasUserField) {
|
|
13582
|
+
throw new Error(`The following functions use userContext() but auth() does not return a user object:
|
|
13583
|
+
` + ` ${functionsWithUserContext.join(", ")}
|
|
13584
|
+
|
|
13585
|
+
` + `To fix this, update your auth function to return an AuthResult:
|
|
13586
|
+
` + ` auth: async (req) => {
|
|
13587
|
+
` + ` return {
|
|
13588
|
+
` + ` groups: ['user'],
|
|
13589
|
+
` + ` user: { id: '...', email: '...' } // Add user data here
|
|
13590
|
+
` + ` };
|
|
13591
|
+
` + ` }`);
|
|
13592
|
+
}
|
|
13593
|
+
} catch (error) {
|
|
13594
|
+
if (error instanceof Error && error.message.includes("userContext")) {
|
|
13595
|
+
throw error;
|
|
13596
|
+
}
|
|
13597
|
+
}
|
|
13598
|
+
}
|
|
13534
13599
|
|
|
13535
13600
|
// src/config/define.ts
|
|
13536
13601
|
function defineOntology(config) {
|
|
@@ -16277,11 +16342,19 @@ function createLogger(debug = false) {
|
|
|
16277
16342
|
}
|
|
16278
16343
|
|
|
16279
16344
|
// src/server/api/middleware.ts
|
|
16345
|
+
function normalizeAuthResult(result) {
|
|
16346
|
+
if (Array.isArray(result)) {
|
|
16347
|
+
return { groups: result };
|
|
16348
|
+
}
|
|
16349
|
+
return result;
|
|
16350
|
+
}
|
|
16280
16351
|
function createAuthMiddleware(config) {
|
|
16281
16352
|
return createMiddleware(async (c3, next) => {
|
|
16282
16353
|
try {
|
|
16283
|
-
const
|
|
16284
|
-
|
|
16354
|
+
const rawResult = await config.auth(c3.req.raw);
|
|
16355
|
+
const authResult = normalizeAuthResult(rawResult);
|
|
16356
|
+
c3.set("authResult", authResult);
|
|
16357
|
+
c3.set("accessGroups", authResult.groups);
|
|
16285
16358
|
await next();
|
|
16286
16359
|
} catch (error) {
|
|
16287
16360
|
return c3.json({
|
|
@@ -16336,15 +16409,24 @@ function errorHandler2() {
|
|
|
16336
16409
|
}
|
|
16337
16410
|
|
|
16338
16411
|
// src/server/api/router.ts
|
|
16412
|
+
init_categorical();
|
|
16339
16413
|
function createApiRoutes(config, configDir) {
|
|
16340
16414
|
const router = new Hono2;
|
|
16341
16415
|
for (const [name, fn] of Object.entries(config.functions)) {
|
|
16342
16416
|
const path = `/${name}`;
|
|
16417
|
+
const userContextFields = getUserContextFields(fn.inputs);
|
|
16343
16418
|
router.post(path, createAccessControlMiddleware(fn.access), async (c3) => {
|
|
16344
16419
|
const resolverContext = c3.get("resolverContext");
|
|
16420
|
+
const authResult = c3.get("authResult");
|
|
16345
16421
|
let args;
|
|
16346
16422
|
try {
|
|
16347
|
-
|
|
16423
|
+
let body = await c3.req.json();
|
|
16424
|
+
if (userContextFields.length > 0 && authResult.user) {
|
|
16425
|
+
body = { ...body };
|
|
16426
|
+
for (const field of userContextFields) {
|
|
16427
|
+
body[field] = authResult.user;
|
|
16428
|
+
}
|
|
16429
|
+
}
|
|
16348
16430
|
const parsed = fn.inputs.safeParse(body);
|
|
16349
16431
|
if (!parsed.success) {
|
|
16350
16432
|
return c3.json({
|
|
@@ -16354,7 +16436,13 @@ function createApiRoutes(config, configDir) {
|
|
|
16354
16436
|
}
|
|
16355
16437
|
args = parsed.data;
|
|
16356
16438
|
} catch {
|
|
16357
|
-
|
|
16439
|
+
let emptyBody = {};
|
|
16440
|
+
if (userContextFields.length > 0 && authResult.user) {
|
|
16441
|
+
for (const field of userContextFields) {
|
|
16442
|
+
emptyBody[field] = authResult.user;
|
|
16443
|
+
}
|
|
16444
|
+
}
|
|
16445
|
+
const parsed = fn.inputs.safeParse(emptyBody);
|
|
16358
16446
|
if (!parsed.success) {
|
|
16359
16447
|
return c3.json({
|
|
16360
16448
|
error: "Validation failed",
|
|
@@ -22822,6 +22910,31 @@ data:
|
|
|
22822
22910
|
init_zod();
|
|
22823
22911
|
init_esm();
|
|
22824
22912
|
init_categorical();
|
|
22913
|
+
function stripUserContextFromJsonSchema(jsonSchema, zodSchema) {
|
|
22914
|
+
if (jsonSchema.type !== "object" || !jsonSchema.properties) {
|
|
22915
|
+
return jsonSchema;
|
|
22916
|
+
}
|
|
22917
|
+
const userContextFields = getUserContextFields(zodSchema);
|
|
22918
|
+
if (userContextFields.length === 0) {
|
|
22919
|
+
return jsonSchema;
|
|
22920
|
+
}
|
|
22921
|
+
const properties = { ...jsonSchema.properties };
|
|
22922
|
+
const required2 = jsonSchema.required ? [...jsonSchema.required] : undefined;
|
|
22923
|
+
for (const field of userContextFields) {
|
|
22924
|
+
delete properties[field];
|
|
22925
|
+
if (required2) {
|
|
22926
|
+
const idx = required2.indexOf(field);
|
|
22927
|
+
if (idx !== -1) {
|
|
22928
|
+
required2.splice(idx, 1);
|
|
22929
|
+
}
|
|
22930
|
+
}
|
|
22931
|
+
}
|
|
22932
|
+
return {
|
|
22933
|
+
...jsonSchema,
|
|
22934
|
+
properties,
|
|
22935
|
+
required: required2 && required2.length > 0 ? required2 : undefined
|
|
22936
|
+
};
|
|
22937
|
+
}
|
|
22825
22938
|
function extractFieldReferencesForMcp(schema, path = "") {
|
|
22826
22939
|
const results = [];
|
|
22827
22940
|
const metadata = getFieldFromMetadata(schema);
|
|
@@ -22861,6 +22974,7 @@ function generateMcpTools(config2) {
|
|
|
22861
22974
|
$refStrategy: "none"
|
|
22862
22975
|
});
|
|
22863
22976
|
delete inputSchema.$schema;
|
|
22977
|
+
inputSchema = stripUserContextFromJsonSchema(inputSchema, fn.inputs);
|
|
22864
22978
|
} catch {
|
|
22865
22979
|
inputSchema = { type: "object", properties: {} };
|
|
22866
22980
|
}
|
|
@@ -22892,16 +23006,28 @@ function filterToolsByAccess(tools, accessGroups) {
|
|
|
22892
23006
|
return tools.filter((tool) => tool.access.some((group) => accessGroups.includes(group)));
|
|
22893
23007
|
}
|
|
22894
23008
|
function createToolExecutor(config2, configDir, env2, envConfig, logger) {
|
|
22895
|
-
|
|
23009
|
+
const userContextFieldsCache = new Map;
|
|
23010
|
+
for (const [name, fn] of Object.entries(config2.functions)) {
|
|
23011
|
+
userContextFieldsCache.set(name, getUserContextFields(fn.inputs));
|
|
23012
|
+
}
|
|
23013
|
+
return async (toolName, args, authResult) => {
|
|
22896
23014
|
const fn = config2.functions[toolName];
|
|
22897
23015
|
if (!fn) {
|
|
22898
23016
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
22899
23017
|
}
|
|
22900
|
-
const hasAccess = fn.access.some((group) =>
|
|
23018
|
+
const hasAccess = fn.access.some((group) => authResult.groups.includes(group));
|
|
22901
23019
|
if (!hasAccess) {
|
|
22902
23020
|
throw new Error(`Access denied to tool "${toolName}". Requires: ${fn.access.join(", ")}`);
|
|
22903
23021
|
}
|
|
22904
|
-
const
|
|
23022
|
+
const userContextFields = userContextFieldsCache.get(toolName) || [];
|
|
23023
|
+
let argsWithContext = args;
|
|
23024
|
+
if (userContextFields.length > 0 && authResult.user) {
|
|
23025
|
+
argsWithContext = { ...args };
|
|
23026
|
+
for (const field of userContextFields) {
|
|
23027
|
+
argsWithContext[field] = authResult.user;
|
|
23028
|
+
}
|
|
23029
|
+
}
|
|
23030
|
+
const parsed = fn.inputs.safeParse(argsWithContext);
|
|
22905
23031
|
if (!parsed.success) {
|
|
22906
23032
|
throw new Error(`Invalid input for tool "${toolName}": ${parsed.error.message}`);
|
|
22907
23033
|
}
|
|
@@ -22909,7 +23035,7 @@ function createToolExecutor(config2, configDir, env2, envConfig, logger) {
|
|
|
22909
23035
|
env: env2,
|
|
22910
23036
|
envConfig,
|
|
22911
23037
|
logger,
|
|
22912
|
-
accessGroups
|
|
23038
|
+
accessGroups: authResult.groups
|
|
22913
23039
|
};
|
|
22914
23040
|
const resolver = await loadResolver(fn.resolver, configDir);
|
|
22915
23041
|
return resolver(resolverContext, parsed.data);
|
|
@@ -22949,10 +23075,20 @@ async function serve2(app, port) {
|
|
|
22949
23075
|
}
|
|
22950
23076
|
|
|
22951
23077
|
// src/server/mcp/index.ts
|
|
22952
|
-
function
|
|
22953
|
-
if (
|
|
22954
|
-
return
|
|
22955
|
-
|
|
23078
|
+
function normalizeAuthResult2(result) {
|
|
23079
|
+
if (Array.isArray(result)) {
|
|
23080
|
+
return { groups: result };
|
|
23081
|
+
}
|
|
23082
|
+
return result;
|
|
23083
|
+
}
|
|
23084
|
+
function getAuthResult(authInfo) {
|
|
23085
|
+
if (!authInfo?.extra?.authResult) {
|
|
23086
|
+
if (authInfo?.extra?.accessGroups) {
|
|
23087
|
+
return { groups: authInfo.extra.accessGroups };
|
|
23088
|
+
}
|
|
23089
|
+
return { groups: [] };
|
|
23090
|
+
}
|
|
23091
|
+
return authInfo.extra.authResult;
|
|
22956
23092
|
}
|
|
22957
23093
|
function createMcpServer(options) {
|
|
22958
23094
|
const { config: config2, configDir, env: env2 } = options;
|
|
@@ -22972,8 +23108,8 @@ function createMcpServer(options) {
|
|
|
22972
23108
|
}
|
|
22973
23109
|
});
|
|
22974
23110
|
server.setRequestHandler(ListToolsRequestSchema, async (_request, extra) => {
|
|
22975
|
-
const
|
|
22976
|
-
const accessibleTools = filterToolsByAccess(allTools,
|
|
23111
|
+
const authResult = getAuthResult(extra.authInfo);
|
|
23112
|
+
const accessibleTools = filterToolsByAccess(allTools, authResult.groups);
|
|
22977
23113
|
return {
|
|
22978
23114
|
tools: accessibleTools.map((tool) => ({
|
|
22979
23115
|
name: tool.name,
|
|
@@ -22984,9 +23120,9 @@ function createMcpServer(options) {
|
|
|
22984
23120
|
});
|
|
22985
23121
|
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
22986
23122
|
const { name, arguments: args } = request.params;
|
|
22987
|
-
const
|
|
23123
|
+
const authResult = getAuthResult(extra.authInfo);
|
|
22988
23124
|
try {
|
|
22989
|
-
const result = await executeToolWithAccess(name, args || {},
|
|
23125
|
+
const result = await executeToolWithAccess(name, args || {}, authResult);
|
|
22990
23126
|
return {
|
|
22991
23127
|
content: [
|
|
22992
23128
|
{
|
|
@@ -23027,13 +23163,15 @@ async function startMcpServer(options) {
|
|
|
23027
23163
|
});
|
|
23028
23164
|
app.all("/mcp", async (c3) => {
|
|
23029
23165
|
try {
|
|
23030
|
-
const
|
|
23166
|
+
const rawResult = await config2.auth(c3.req.raw);
|
|
23167
|
+
const authResult = normalizeAuthResult2(rawResult);
|
|
23031
23168
|
const authInfo = {
|
|
23032
23169
|
token: c3.req.header("Authorization") || "",
|
|
23033
23170
|
clientId: "ontology-client",
|
|
23034
23171
|
scopes: [],
|
|
23035
23172
|
extra: {
|
|
23036
|
-
|
|
23173
|
+
authResult,
|
|
23174
|
+
accessGroups: authResult.groups
|
|
23037
23175
|
}
|
|
23038
23176
|
};
|
|
23039
23177
|
return transport.handleRequest(c3.req.raw, { authInfo });
|
|
@@ -23071,6 +23209,12 @@ async function startOnt(options = {}) {
|
|
|
23071
23209
|
const isDev = mode === "development";
|
|
23072
23210
|
consola.info("Loading ontology config...");
|
|
23073
23211
|
const { config: config2, configDir } = await loadConfig();
|
|
23212
|
+
try {
|
|
23213
|
+
await validateUserContextRequirements(config2);
|
|
23214
|
+
} catch (error2) {
|
|
23215
|
+
consola.error("User context validation failed:");
|
|
23216
|
+
throw error2;
|
|
23217
|
+
}
|
|
23074
23218
|
consola.info("Checking lockfile...");
|
|
23075
23219
|
const { ontology, hash } = computeOntologyHash(config2);
|
|
23076
23220
|
if (!lockfileExists(configDir)) {
|
|
@@ -23146,6 +23290,7 @@ Run \`bun run review\` to approve the changes.`;
|
|
|
23146
23290
|
init_zod();
|
|
23147
23291
|
export {
|
|
23148
23292
|
exports_external as z,
|
|
23293
|
+
userContext,
|
|
23149
23294
|
startOnt,
|
|
23150
23295
|
fieldFrom,
|
|
23151
23296
|
defineOntology
|
|
@@ -3,6 +3,10 @@ import { z } from "zod";
|
|
|
3
3
|
* Symbol for storing fieldFrom metadata on Zod schemas
|
|
4
4
|
*/
|
|
5
5
|
export declare const FIELD_FROM_METADATA: unique symbol;
|
|
6
|
+
/**
|
|
7
|
+
* Symbol for storing userContext metadata on Zod schemas
|
|
8
|
+
*/
|
|
9
|
+
export declare const USER_CONTEXT_METADATA: unique symbol;
|
|
6
10
|
/**
|
|
7
11
|
* Metadata stored on fieldFrom Zod schemas
|
|
8
12
|
*/
|
|
@@ -74,3 +78,58 @@ export declare function hasFieldFromMetadata(schema: unknown): schema is FieldFr
|
|
|
74
78
|
* Extract fieldFrom metadata from a Zod schema
|
|
75
79
|
*/
|
|
76
80
|
export declare function getFieldFromMetadata(schema: unknown): FieldFromMetadata | null;
|
|
81
|
+
/**
|
|
82
|
+
* Type for a Zod schema with userContext metadata
|
|
83
|
+
*/
|
|
84
|
+
export type UserContextSchema<T extends z.ZodType> = T & {
|
|
85
|
+
[USER_CONTEXT_METADATA]: true;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Mark a schema as user context that will be injected at runtime.
|
|
89
|
+
*
|
|
90
|
+
* Fields marked with `userContext()` are:
|
|
91
|
+
* - **Injected**: Populated from `auth()` result's `user` field
|
|
92
|
+
* - **Hidden**: Not exposed in public API/MCP schemas
|
|
93
|
+
* - **Type-safe**: Resolver receives typed user object
|
|
94
|
+
*
|
|
95
|
+
* @param schema - Zod schema for the user context shape
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* defineOntology({
|
|
100
|
+
* auth: async (req) => {
|
|
101
|
+
* const user = await verifyToken(req);
|
|
102
|
+
* return {
|
|
103
|
+
* groups: user.isAdmin ? ['admin'] : ['user'],
|
|
104
|
+
* user: { id: user.id, email: user.email }
|
|
105
|
+
* };
|
|
106
|
+
* },
|
|
107
|
+
*
|
|
108
|
+
* functions: {
|
|
109
|
+
* editPost: {
|
|
110
|
+
* description: 'Edit a post',
|
|
111
|
+
* access: ['user', 'admin'],
|
|
112
|
+
* entities: ['Post'],
|
|
113
|
+
* inputs: z.object({
|
|
114
|
+
* postId: z.string(),
|
|
115
|
+
* title: z.string(),
|
|
116
|
+
* currentUser: userContext(z.object({
|
|
117
|
+
* id: z.string(),
|
|
118
|
+
* email: z.string(),
|
|
119
|
+
* })),
|
|
120
|
+
* }),
|
|
121
|
+
* resolver: './resolvers/editPost.ts',
|
|
122
|
+
* },
|
|
123
|
+
* },
|
|
124
|
+
* })
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
export declare function userContext<T extends z.ZodType>(schema: T): UserContextSchema<T>;
|
|
128
|
+
/**
|
|
129
|
+
* Check if a Zod schema has userContext metadata
|
|
130
|
+
*/
|
|
131
|
+
export declare function hasUserContextMetadata(schema: unknown): schema is UserContextSchema<z.ZodType>;
|
|
132
|
+
/**
|
|
133
|
+
* Get all userContext field names from a Zod object schema
|
|
134
|
+
*/
|
|
135
|
+
export declare function getUserContextFields(schema: z.ZodType): string[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { defineOntology } from "./define.js";
|
|
2
|
-
export { fieldFrom } from "./categorical.js";
|
|
3
|
-
export type { OntologyConfig, FunctionDefinition, AccessGroupConfig, EnvironmentConfig, EntityDefinition, AuthFunction, ResolverContext, ResolverFunction, FieldOption, } from "./types.js";
|
|
4
|
-
export { OntologyConfigSchema, FunctionDefinitionSchema, AccessGroupConfigSchema, EnvironmentConfigSchema, EntityDefinitionSchema, validateAccessGroups, validateEntityReferences, validateFieldFromReferences, } from "./schema.js";
|
|
2
|
+
export { fieldFrom, userContext } from "./categorical.js";
|
|
3
|
+
export type { OntologyConfig, FunctionDefinition, AccessGroupConfig, EnvironmentConfig, EntityDefinition, AuthFunction, AuthResult, ResolverContext, ResolverFunction, FieldOption, } from "./types.js";
|
|
4
|
+
export { OntologyConfigSchema, FunctionDefinitionSchema, AccessGroupConfigSchema, EnvironmentConfigSchema, EntityDefinitionSchema, validateAccessGroups, validateEntityReferences, validateFieldFromReferences, validateUserContextRequirements, } from "./schema.js";
|