ont-run 0.0.1 → 0.0.3
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 +113 -15
- package/dist/index.js +181 -25
- 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/cli/utils/config-loader.ts +18 -3
- 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:
|
|
@@ -8598,10 +8656,21 @@ async function loadConfig(configPath) {
|
|
|
8598
8656
|
}
|
|
8599
8657
|
return { config, configDir, configPath: resolvedPath };
|
|
8600
8658
|
} catch (error) {
|
|
8601
|
-
|
|
8602
|
-
|
|
8659
|
+
const err = error;
|
|
8660
|
+
if (err.code === "ERR_MODULE_NOT_FOUND") {
|
|
8661
|
+
const message = err.message || "";
|
|
8662
|
+
if (message.includes("ont-run") || message.includes("zod")) {
|
|
8663
|
+
throw new Error(`Failed to load config: ${resolvedPath}
|
|
8664
|
+
|
|
8665
|
+
Missing dependencies. Run 'bun install' first.`);
|
|
8666
|
+
}
|
|
8667
|
+
throw new Error(`Failed to load config: ${resolvedPath}
|
|
8668
|
+
|
|
8669
|
+
Module not found: ${message}`);
|
|
8603
8670
|
}
|
|
8604
|
-
throw
|
|
8671
|
+
throw new Error(`Failed to load config: ${resolvedPath}
|
|
8672
|
+
|
|
8673
|
+
${err.message || error}`);
|
|
8605
8674
|
}
|
|
8606
8675
|
}
|
|
8607
8676
|
|
|
@@ -10786,6 +10855,8 @@ function transformToGraphData(config) {
|
|
|
10786
10855
|
for (const entity of fn.entities) {
|
|
10787
10856
|
entityCounts[entity] = (entityCounts[entity] || 0) + 1;
|
|
10788
10857
|
}
|
|
10858
|
+
const userContextFields = getUserContextFields(fn.inputs);
|
|
10859
|
+
const usesUserContext = userContextFields.length > 0;
|
|
10789
10860
|
nodes.push({
|
|
10790
10861
|
id: `function:${name}`,
|
|
10791
10862
|
type: "function",
|
|
@@ -10794,7 +10865,8 @@ function transformToGraphData(config) {
|
|
|
10794
10865
|
metadata: {
|
|
10795
10866
|
inputs: safeZodToJsonSchema(fn.inputs),
|
|
10796
10867
|
outputs: fn.outputs ? safeZodToJsonSchema(fn.outputs) : undefined,
|
|
10797
|
-
resolver: fn.resolver
|
|
10868
|
+
resolver: fn.resolver,
|
|
10869
|
+
usesUserContext: usesUserContext || undefined
|
|
10798
10870
|
}
|
|
10799
10871
|
});
|
|
10800
10872
|
for (const group of fn.access) {
|
|
@@ -11386,6 +11458,27 @@ function generateBrowserUI(graphData) {
|
|
|
11386
11458
|
color: var(--change-modified);
|
|
11387
11459
|
}
|
|
11388
11460
|
|
|
11461
|
+
/* User Context Badge */
|
|
11462
|
+
.user-context-badge {
|
|
11463
|
+
display: inline-flex;
|
|
11464
|
+
align-items: center;
|
|
11465
|
+
gap: 4px;
|
|
11466
|
+
padding: 2px 8px;
|
|
11467
|
+
border-radius: 9999px;
|
|
11468
|
+
font-size: 10px;
|
|
11469
|
+
font-weight: 500;
|
|
11470
|
+
background: rgba(21, 168, 168, 0.12);
|
|
11471
|
+
color: var(--vanna-teal);
|
|
11472
|
+
text-transform: uppercase;
|
|
11473
|
+
letter-spacing: 0.03em;
|
|
11474
|
+
margin-left: 8px;
|
|
11475
|
+
}
|
|
11476
|
+
|
|
11477
|
+
.user-context-badge svg {
|
|
11478
|
+
width: 12px;
|
|
11479
|
+
height: 12px;
|
|
11480
|
+
}
|
|
11481
|
+
|
|
11389
11482
|
/* Review Footer */
|
|
11390
11483
|
.review-footer {
|
|
11391
11484
|
position: fixed;
|
|
@@ -12854,9 +12947,14 @@ function generateBrowserUI(graphData) {
|
|
|
12854
12947
|
? \`<span class="detail-change-badge \${changeStatus}">\${changeStatus === 'added' ? 'New' : changeStatus === 'removed' ? 'Removed' : 'Modified'}</span>\`
|
|
12855
12948
|
: '';
|
|
12856
12949
|
|
|
12950
|
+
// Build user context badge if applicable
|
|
12951
|
+
const userContextBadge = data.metadata?.usesUserContext
|
|
12952
|
+
? \`<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>\`
|
|
12953
|
+
: '';
|
|
12954
|
+
|
|
12857
12955
|
let html = \`
|
|
12858
12956
|
<div class="detail-header">
|
|
12859
|
-
<div class="detail-type \${data.type}">\${formatType(data.type)}\${changeBadge}</div>
|
|
12957
|
+
<div class="detail-type \${data.type}">\${formatType(data.type)}\${changeBadge}\${userContextBadge}</div>
|
|
12860
12958
|
<div class="detail-name">\${data.label}</div>
|
|
12861
12959
|
<div class="detail-description">\${data.description || 'No description'}</div>
|
|
12862
12960
|
</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) {
|
|
@@ -14558,10 +14623,21 @@ async function loadConfig(configPath) {
|
|
|
14558
14623
|
}
|
|
14559
14624
|
return { config, configDir, configPath: resolvedPath };
|
|
14560
14625
|
} catch (error) {
|
|
14561
|
-
|
|
14562
|
-
|
|
14626
|
+
const err = error;
|
|
14627
|
+
if (err.code === "ERR_MODULE_NOT_FOUND") {
|
|
14628
|
+
const message = err.message || "";
|
|
14629
|
+
if (message.includes("ont-run") || message.includes("zod")) {
|
|
14630
|
+
throw new Error(`Failed to load config: ${resolvedPath}
|
|
14631
|
+
|
|
14632
|
+
Missing dependencies. Run 'bun install' first.`);
|
|
14633
|
+
}
|
|
14634
|
+
throw new Error(`Failed to load config: ${resolvedPath}
|
|
14635
|
+
|
|
14636
|
+
Module not found: ${message}`);
|
|
14563
14637
|
}
|
|
14564
|
-
throw
|
|
14638
|
+
throw new Error(`Failed to load config: ${resolvedPath}
|
|
14639
|
+
|
|
14640
|
+
${err.message || error}`);
|
|
14565
14641
|
}
|
|
14566
14642
|
}
|
|
14567
14643
|
|
|
@@ -16277,11 +16353,19 @@ function createLogger(debug = false) {
|
|
|
16277
16353
|
}
|
|
16278
16354
|
|
|
16279
16355
|
// src/server/api/middleware.ts
|
|
16356
|
+
function normalizeAuthResult(result) {
|
|
16357
|
+
if (Array.isArray(result)) {
|
|
16358
|
+
return { groups: result };
|
|
16359
|
+
}
|
|
16360
|
+
return result;
|
|
16361
|
+
}
|
|
16280
16362
|
function createAuthMiddleware(config) {
|
|
16281
16363
|
return createMiddleware(async (c3, next) => {
|
|
16282
16364
|
try {
|
|
16283
|
-
const
|
|
16284
|
-
|
|
16365
|
+
const rawResult = await config.auth(c3.req.raw);
|
|
16366
|
+
const authResult = normalizeAuthResult(rawResult);
|
|
16367
|
+
c3.set("authResult", authResult);
|
|
16368
|
+
c3.set("accessGroups", authResult.groups);
|
|
16285
16369
|
await next();
|
|
16286
16370
|
} catch (error) {
|
|
16287
16371
|
return c3.json({
|
|
@@ -16336,15 +16420,24 @@ function errorHandler2() {
|
|
|
16336
16420
|
}
|
|
16337
16421
|
|
|
16338
16422
|
// src/server/api/router.ts
|
|
16423
|
+
init_categorical();
|
|
16339
16424
|
function createApiRoutes(config, configDir) {
|
|
16340
16425
|
const router = new Hono2;
|
|
16341
16426
|
for (const [name, fn] of Object.entries(config.functions)) {
|
|
16342
16427
|
const path = `/${name}`;
|
|
16428
|
+
const userContextFields = getUserContextFields(fn.inputs);
|
|
16343
16429
|
router.post(path, createAccessControlMiddleware(fn.access), async (c3) => {
|
|
16344
16430
|
const resolverContext = c3.get("resolverContext");
|
|
16431
|
+
const authResult = c3.get("authResult");
|
|
16345
16432
|
let args;
|
|
16346
16433
|
try {
|
|
16347
|
-
|
|
16434
|
+
let body = await c3.req.json();
|
|
16435
|
+
if (userContextFields.length > 0 && authResult.user) {
|
|
16436
|
+
body = { ...body };
|
|
16437
|
+
for (const field of userContextFields) {
|
|
16438
|
+
body[field] = authResult.user;
|
|
16439
|
+
}
|
|
16440
|
+
}
|
|
16348
16441
|
const parsed = fn.inputs.safeParse(body);
|
|
16349
16442
|
if (!parsed.success) {
|
|
16350
16443
|
return c3.json({
|
|
@@ -16354,7 +16447,13 @@ function createApiRoutes(config, configDir) {
|
|
|
16354
16447
|
}
|
|
16355
16448
|
args = parsed.data;
|
|
16356
16449
|
} catch {
|
|
16357
|
-
|
|
16450
|
+
let emptyBody = {};
|
|
16451
|
+
if (userContextFields.length > 0 && authResult.user) {
|
|
16452
|
+
for (const field of userContextFields) {
|
|
16453
|
+
emptyBody[field] = authResult.user;
|
|
16454
|
+
}
|
|
16455
|
+
}
|
|
16456
|
+
const parsed = fn.inputs.safeParse(emptyBody);
|
|
16358
16457
|
if (!parsed.success) {
|
|
16359
16458
|
return c3.json({
|
|
16360
16459
|
error: "Validation failed",
|
|
@@ -22822,6 +22921,31 @@ data:
|
|
|
22822
22921
|
init_zod();
|
|
22823
22922
|
init_esm();
|
|
22824
22923
|
init_categorical();
|
|
22924
|
+
function stripUserContextFromJsonSchema(jsonSchema, zodSchema) {
|
|
22925
|
+
if (jsonSchema.type !== "object" || !jsonSchema.properties) {
|
|
22926
|
+
return jsonSchema;
|
|
22927
|
+
}
|
|
22928
|
+
const userContextFields = getUserContextFields(zodSchema);
|
|
22929
|
+
if (userContextFields.length === 0) {
|
|
22930
|
+
return jsonSchema;
|
|
22931
|
+
}
|
|
22932
|
+
const properties = { ...jsonSchema.properties };
|
|
22933
|
+
const required2 = jsonSchema.required ? [...jsonSchema.required] : undefined;
|
|
22934
|
+
for (const field of userContextFields) {
|
|
22935
|
+
delete properties[field];
|
|
22936
|
+
if (required2) {
|
|
22937
|
+
const idx = required2.indexOf(field);
|
|
22938
|
+
if (idx !== -1) {
|
|
22939
|
+
required2.splice(idx, 1);
|
|
22940
|
+
}
|
|
22941
|
+
}
|
|
22942
|
+
}
|
|
22943
|
+
return {
|
|
22944
|
+
...jsonSchema,
|
|
22945
|
+
properties,
|
|
22946
|
+
required: required2 && required2.length > 0 ? required2 : undefined
|
|
22947
|
+
};
|
|
22948
|
+
}
|
|
22825
22949
|
function extractFieldReferencesForMcp(schema, path = "") {
|
|
22826
22950
|
const results = [];
|
|
22827
22951
|
const metadata = getFieldFromMetadata(schema);
|
|
@@ -22861,6 +22985,7 @@ function generateMcpTools(config2) {
|
|
|
22861
22985
|
$refStrategy: "none"
|
|
22862
22986
|
});
|
|
22863
22987
|
delete inputSchema.$schema;
|
|
22988
|
+
inputSchema = stripUserContextFromJsonSchema(inputSchema, fn.inputs);
|
|
22864
22989
|
} catch {
|
|
22865
22990
|
inputSchema = { type: "object", properties: {} };
|
|
22866
22991
|
}
|
|
@@ -22892,16 +23017,28 @@ function filterToolsByAccess(tools, accessGroups) {
|
|
|
22892
23017
|
return tools.filter((tool) => tool.access.some((group) => accessGroups.includes(group)));
|
|
22893
23018
|
}
|
|
22894
23019
|
function createToolExecutor(config2, configDir, env2, envConfig, logger) {
|
|
22895
|
-
|
|
23020
|
+
const userContextFieldsCache = new Map;
|
|
23021
|
+
for (const [name, fn] of Object.entries(config2.functions)) {
|
|
23022
|
+
userContextFieldsCache.set(name, getUserContextFields(fn.inputs));
|
|
23023
|
+
}
|
|
23024
|
+
return async (toolName, args, authResult) => {
|
|
22896
23025
|
const fn = config2.functions[toolName];
|
|
22897
23026
|
if (!fn) {
|
|
22898
23027
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
22899
23028
|
}
|
|
22900
|
-
const hasAccess = fn.access.some((group) =>
|
|
23029
|
+
const hasAccess = fn.access.some((group) => authResult.groups.includes(group));
|
|
22901
23030
|
if (!hasAccess) {
|
|
22902
23031
|
throw new Error(`Access denied to tool "${toolName}". Requires: ${fn.access.join(", ")}`);
|
|
22903
23032
|
}
|
|
22904
|
-
const
|
|
23033
|
+
const userContextFields = userContextFieldsCache.get(toolName) || [];
|
|
23034
|
+
let argsWithContext = args;
|
|
23035
|
+
if (userContextFields.length > 0 && authResult.user) {
|
|
23036
|
+
argsWithContext = { ...args };
|
|
23037
|
+
for (const field of userContextFields) {
|
|
23038
|
+
argsWithContext[field] = authResult.user;
|
|
23039
|
+
}
|
|
23040
|
+
}
|
|
23041
|
+
const parsed = fn.inputs.safeParse(argsWithContext);
|
|
22905
23042
|
if (!parsed.success) {
|
|
22906
23043
|
throw new Error(`Invalid input for tool "${toolName}": ${parsed.error.message}`);
|
|
22907
23044
|
}
|
|
@@ -22909,7 +23046,7 @@ function createToolExecutor(config2, configDir, env2, envConfig, logger) {
|
|
|
22909
23046
|
env: env2,
|
|
22910
23047
|
envConfig,
|
|
22911
23048
|
logger,
|
|
22912
|
-
accessGroups
|
|
23049
|
+
accessGroups: authResult.groups
|
|
22913
23050
|
};
|
|
22914
23051
|
const resolver = await loadResolver(fn.resolver, configDir);
|
|
22915
23052
|
return resolver(resolverContext, parsed.data);
|
|
@@ -22949,10 +23086,20 @@ async function serve2(app, port) {
|
|
|
22949
23086
|
}
|
|
22950
23087
|
|
|
22951
23088
|
// src/server/mcp/index.ts
|
|
22952
|
-
function
|
|
22953
|
-
if (
|
|
22954
|
-
return
|
|
22955
|
-
|
|
23089
|
+
function normalizeAuthResult2(result) {
|
|
23090
|
+
if (Array.isArray(result)) {
|
|
23091
|
+
return { groups: result };
|
|
23092
|
+
}
|
|
23093
|
+
return result;
|
|
23094
|
+
}
|
|
23095
|
+
function getAuthResult(authInfo) {
|
|
23096
|
+
if (!authInfo?.extra?.authResult) {
|
|
23097
|
+
if (authInfo?.extra?.accessGroups) {
|
|
23098
|
+
return { groups: authInfo.extra.accessGroups };
|
|
23099
|
+
}
|
|
23100
|
+
return { groups: [] };
|
|
23101
|
+
}
|
|
23102
|
+
return authInfo.extra.authResult;
|
|
22956
23103
|
}
|
|
22957
23104
|
function createMcpServer(options) {
|
|
22958
23105
|
const { config: config2, configDir, env: env2 } = options;
|
|
@@ -22972,8 +23119,8 @@ function createMcpServer(options) {
|
|
|
22972
23119
|
}
|
|
22973
23120
|
});
|
|
22974
23121
|
server.setRequestHandler(ListToolsRequestSchema, async (_request, extra) => {
|
|
22975
|
-
const
|
|
22976
|
-
const accessibleTools = filterToolsByAccess(allTools,
|
|
23122
|
+
const authResult = getAuthResult(extra.authInfo);
|
|
23123
|
+
const accessibleTools = filterToolsByAccess(allTools, authResult.groups);
|
|
22977
23124
|
return {
|
|
22978
23125
|
tools: accessibleTools.map((tool) => ({
|
|
22979
23126
|
name: tool.name,
|
|
@@ -22984,9 +23131,9 @@ function createMcpServer(options) {
|
|
|
22984
23131
|
});
|
|
22985
23132
|
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
22986
23133
|
const { name, arguments: args } = request.params;
|
|
22987
|
-
const
|
|
23134
|
+
const authResult = getAuthResult(extra.authInfo);
|
|
22988
23135
|
try {
|
|
22989
|
-
const result = await executeToolWithAccess(name, args || {},
|
|
23136
|
+
const result = await executeToolWithAccess(name, args || {}, authResult);
|
|
22990
23137
|
return {
|
|
22991
23138
|
content: [
|
|
22992
23139
|
{
|
|
@@ -23027,13 +23174,15 @@ async function startMcpServer(options) {
|
|
|
23027
23174
|
});
|
|
23028
23175
|
app.all("/mcp", async (c3) => {
|
|
23029
23176
|
try {
|
|
23030
|
-
const
|
|
23177
|
+
const rawResult = await config2.auth(c3.req.raw);
|
|
23178
|
+
const authResult = normalizeAuthResult2(rawResult);
|
|
23031
23179
|
const authInfo = {
|
|
23032
23180
|
token: c3.req.header("Authorization") || "",
|
|
23033
23181
|
clientId: "ontology-client",
|
|
23034
23182
|
scopes: [],
|
|
23035
23183
|
extra: {
|
|
23036
|
-
|
|
23184
|
+
authResult,
|
|
23185
|
+
accessGroups: authResult.groups
|
|
23037
23186
|
}
|
|
23038
23187
|
};
|
|
23039
23188
|
return transport.handleRequest(c3.req.raw, { authInfo });
|
|
@@ -23071,6 +23220,12 @@ async function startOnt(options = {}) {
|
|
|
23071
23220
|
const isDev = mode === "development";
|
|
23072
23221
|
consola.info("Loading ontology config...");
|
|
23073
23222
|
const { config: config2, configDir } = await loadConfig();
|
|
23223
|
+
try {
|
|
23224
|
+
await validateUserContextRequirements(config2);
|
|
23225
|
+
} catch (error2) {
|
|
23226
|
+
consola.error("User context validation failed:");
|
|
23227
|
+
throw error2;
|
|
23228
|
+
}
|
|
23074
23229
|
consola.info("Checking lockfile...");
|
|
23075
23230
|
const { ontology, hash } = computeOntologyHash(config2);
|
|
23076
23231
|
if (!lockfileExists(configDir)) {
|
|
@@ -23146,6 +23301,7 @@ Run \`bun run review\` to approve the changes.`;
|
|
|
23146
23301
|
init_zod();
|
|
23147
23302
|
export {
|
|
23148
23303
|
exports_external as z,
|
|
23304
|
+
userContext,
|
|
23149
23305
|
startOnt,
|
|
23150
23306
|
fieldFrom,
|
|
23151
23307
|
defineOntology
|