@objectstack/runtime 12.6.0 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +112 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -7
- package/dist/index.d.ts +19 -7
- package/dist/index.js +109 -22
- package/dist/index.js.map +1 -1
- package/package.json +20 -21
package/dist/index.cjs
CHANGED
|
@@ -1054,7 +1054,7 @@ var init_app_plugin = __esm({
|
|
|
1054
1054
|
if (typeof metadata?.registerInMemory === "function") {
|
|
1055
1055
|
const securityBundle = this.bundle.manifest ? { ...this.bundle.manifest, ...this.bundle } : this.bundle;
|
|
1056
1056
|
const SECURITY_FIELDS = [
|
|
1057
|
-
["
|
|
1057
|
+
["positions", "position"],
|
|
1058
1058
|
["permissions", "permission"],
|
|
1059
1059
|
["sharingRules", "sharing_rule"],
|
|
1060
1060
|
["policies", "policy"]
|
|
@@ -1420,8 +1420,7 @@ var init_app_plugin = __esm({
|
|
|
1420
1420
|
"skills",
|
|
1421
1421
|
"actions",
|
|
1422
1422
|
"permissions",
|
|
1423
|
-
"
|
|
1424
|
-
"profiles",
|
|
1423
|
+
"positions",
|
|
1425
1424
|
"translations",
|
|
1426
1425
|
"sharingRules",
|
|
1427
1426
|
"ragPipelines",
|
|
@@ -1776,7 +1775,7 @@ async function createStandaloneStack(config) {
|
|
|
1776
1775
|
const objects = Array.isArray(artifactBundle?.objects) ? artifactBundle.objects : void 0;
|
|
1777
1776
|
const manifest = artifactBundle?.manifest;
|
|
1778
1777
|
const permissions = Array.isArray(artifactBundle?.permissions) ? artifactBundle.permissions : void 0;
|
|
1779
|
-
const
|
|
1778
|
+
const positions = Array.isArray(artifactBundle?.positions) ? artifactBundle.positions : void 0;
|
|
1780
1779
|
return {
|
|
1781
1780
|
plugins,
|
|
1782
1781
|
api: {
|
|
@@ -1787,7 +1786,7 @@ async function createStandaloneStack(config) {
|
|
|
1787
1786
|
...objects ? { objects } : {},
|
|
1788
1787
|
...manifest ? { manifest } : {},
|
|
1789
1788
|
...permissions ? { permissions } : {},
|
|
1790
|
-
...
|
|
1789
|
+
...positions ? { positions } : {}
|
|
1791
1790
|
};
|
|
1792
1791
|
}
|
|
1793
1792
|
var import_node_path3, import_node_fs2, import_node_os, import_zod, import_types2, StandaloneStackConfigSchema;
|
|
@@ -1858,7 +1857,7 @@ __export(index_exports, {
|
|
|
1858
1857
|
SandboxError: () => SandboxError,
|
|
1859
1858
|
SeedLoaderService: () => import_objectql.SeedLoaderService,
|
|
1860
1859
|
UnimplementedScriptRunner: () => UnimplementedScriptRunner,
|
|
1861
|
-
_resetEnvDeprecationWarnings: () =>
|
|
1860
|
+
_resetEnvDeprecationWarnings: () => import_types4._resetEnvDeprecationWarnings,
|
|
1862
1861
|
actionBodyRunnerFactory: () => actionBodyRunnerFactory,
|
|
1863
1862
|
buildSecurityHeaders: () => buildSecurityHeaders,
|
|
1864
1863
|
collectBundleActions: () => collectBundleActions,
|
|
@@ -1879,7 +1878,7 @@ __export(index_exports, {
|
|
|
1879
1878
|
mergeRuntimeModule: () => mergeRuntimeModule,
|
|
1880
1879
|
parseTraceparent: () => parseTraceparent,
|
|
1881
1880
|
readArtifactSource: () => readArtifactSource,
|
|
1882
|
-
readEnvWithDeprecation: () =>
|
|
1881
|
+
readEnvWithDeprecation: () => import_types4.readEnvWithDeprecation,
|
|
1883
1882
|
resolveDefaultArtifactPath: () => resolveDefaultArtifactPath,
|
|
1884
1883
|
resolveErrorReporter: () => resolveErrorReporter,
|
|
1885
1884
|
resolveMetrics: () => resolveMetrics,
|
|
@@ -2160,7 +2159,9 @@ function safeGet(ctx, name) {
|
|
|
2160
2159
|
|
|
2161
2160
|
// src/http-dispatcher.ts
|
|
2162
2161
|
var import_core5 = require("@objectstack/core");
|
|
2162
|
+
var import_types3 = require("@objectstack/types");
|
|
2163
2163
|
var import_system = require("@objectstack/spec/system");
|
|
2164
|
+
var import_ai = require("@objectstack/spec/ai");
|
|
2164
2165
|
var import_shared2 = require("@objectstack/spec/shared");
|
|
2165
2166
|
init_package_state_store();
|
|
2166
2167
|
|
|
@@ -2195,6 +2196,12 @@ function checkApiExposure(def, action) {
|
|
|
2195
2196
|
|
|
2196
2197
|
// src/security/resolve-execution-context.ts
|
|
2197
2198
|
var import_core3 = require("@objectstack/core");
|
|
2199
|
+
function extractJwtBearer(headers) {
|
|
2200
|
+
const auth = headers.get("authorization");
|
|
2201
|
+
const bearer = auth?.match(/^Bearer\s+(\S+)$/i)?.[1];
|
|
2202
|
+
if (!bearer || bearer.startsWith("osk_")) return void 0;
|
|
2203
|
+
return bearer.split(".").length === 3 ? bearer : void 0;
|
|
2204
|
+
}
|
|
2198
2205
|
function toHeaders(input) {
|
|
2199
2206
|
if (!input) return new Headers();
|
|
2200
2207
|
if (typeof Headers !== "undefined" && input instanceof Headers) return input;
|
|
@@ -2213,6 +2220,22 @@ function toHeaders(input) {
|
|
|
2213
2220
|
async function resolveExecutionContext(opts) {
|
|
2214
2221
|
const headers = toHeaders(opts.request?.headers);
|
|
2215
2222
|
const ql = await opts.getQl();
|
|
2223
|
+
let oauthPrincipal;
|
|
2224
|
+
let oauthBearerPresented = false;
|
|
2225
|
+
if (opts.acceptOAuthAccessToken) {
|
|
2226
|
+
const jwtBearer = extractJwtBearer(headers);
|
|
2227
|
+
if (jwtBearer) {
|
|
2228
|
+
oauthBearerPresented = true;
|
|
2229
|
+
try {
|
|
2230
|
+
const authService = await opts.getService("auth");
|
|
2231
|
+
const verified = await authService?.verifyMcpAccessToken?.(jwtBearer);
|
|
2232
|
+
if (verified?.userId && Array.isArray(verified.scopes)) {
|
|
2233
|
+
oauthPrincipal = verified;
|
|
2234
|
+
}
|
|
2235
|
+
} catch {
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2216
2239
|
const getSession = async (h) => {
|
|
2217
2240
|
try {
|
|
2218
2241
|
const authService = await opts.getService("auth");
|
|
@@ -2223,19 +2246,29 @@ async function resolveExecutionContext(opts) {
|
|
|
2223
2246
|
return void 0;
|
|
2224
2247
|
}
|
|
2225
2248
|
};
|
|
2226
|
-
const
|
|
2249
|
+
const getSessionForProvenance = oauthPrincipal ? async () => ({ user: { id: oauthPrincipal.userId } }) : oauthBearerPresented ? async () => void 0 : getSession;
|
|
2250
|
+
const authz = await (0, import_core3.resolveAuthzContext)({ ql, headers, getSession: getSessionForProvenance });
|
|
2227
2251
|
const ctx = {
|
|
2228
|
-
|
|
2252
|
+
positions: authz.positions,
|
|
2229
2253
|
permissions: authz.permissions,
|
|
2230
2254
|
systemPermissions: authz.systemPermissions,
|
|
2231
2255
|
isSystem: false
|
|
2232
2256
|
};
|
|
2257
|
+
if (authz.userId) {
|
|
2258
|
+
ctx.principalKind = "human";
|
|
2259
|
+
} else {
|
|
2260
|
+
ctx.principalKind = "guest";
|
|
2261
|
+
ctx.positions = ["guest"];
|
|
2262
|
+
}
|
|
2233
2263
|
if (authz.userId) ctx.userId = authz.userId;
|
|
2234
2264
|
if (authz.tenantId) ctx.tenantId = authz.tenantId;
|
|
2235
2265
|
if (authz.email) ctx.email = authz.email;
|
|
2236
2266
|
if (authz.accessToken) ctx.accessToken = authz.accessToken;
|
|
2237
2267
|
if (authz.tabPermissions) ctx.tabPermissions = authz.tabPermissions;
|
|
2238
2268
|
ctx.org_user_ids = authz.org_user_ids;
|
|
2269
|
+
if (oauthPrincipal && ctx.userId === oauthPrincipal.userId) {
|
|
2270
|
+
ctx.oauthScopes = oauthPrincipal.scopes;
|
|
2271
|
+
}
|
|
2239
2272
|
if (authz.userId) {
|
|
2240
2273
|
const settings = await Promise.resolve(opts.getService("settings")).catch(() => void 0);
|
|
2241
2274
|
const localization = await (0, import_core3.resolveLocalizationContext)({
|
|
@@ -2479,9 +2512,10 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
2479
2512
|
* Handle an MCP request over the Streamable HTTP transport (`/mcp`).
|
|
2480
2513
|
*
|
|
2481
2514
|
* Gating + auth (fail-closed):
|
|
2482
|
-
* - **
|
|
2483
|
-
* runtime
|
|
2484
|
-
* return 404 so the
|
|
2515
|
+
* - **default-on**: served unless `OS_MCP_SERVER_ENABLED=false` (single-env
|
|
2516
|
+
* runtime; MCP is a core platform capability). Multi-tenant cloud
|
|
2517
|
+
* overrides this gate per env. When opted out we return 404 so the
|
|
2518
|
+
* surface isn't advertised.
|
|
2485
2519
|
* - **auth**: requires a principal already resolved by
|
|
2486
2520
|
* `resolveExecutionContext` (the `sys_api_key` Bearer/header path or a
|
|
2487
2521
|
* session). Anonymous → 401.
|
|
@@ -2501,7 +2535,29 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
2501
2535
|
}
|
|
2502
2536
|
const ec = context.executionContext;
|
|
2503
2537
|
if (!ec || !ec.userId && !ec.isSystem) {
|
|
2504
|
-
|
|
2538
|
+
const resourceMetadataUrl = await this.getMcpResourceMetadataUrl(context);
|
|
2539
|
+
const response = this.error(
|
|
2540
|
+
resourceMetadataUrl ? "Unauthorized: a valid OAuth access token or API key is required" : "Unauthorized: a valid API key is required",
|
|
2541
|
+
401
|
|
2542
|
+
);
|
|
2543
|
+
if (resourceMetadataUrl) {
|
|
2544
|
+
response.headers = {
|
|
2545
|
+
"WWW-Authenticate": `Bearer realm="ObjectStack MCP", resource_metadata="${resourceMetadataUrl}"`
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
return { handled: true, response };
|
|
2549
|
+
}
|
|
2550
|
+
const grantedScopes = Array.isArray(ec.oauthScopes) ? ec.oauthScopes : void 0;
|
|
2551
|
+
if (grantedScopes && !grantedScopes.some((s) => import_ai.MCP_OAUTH_SCOPES.includes(s))) {
|
|
2552
|
+
const resourceMetadataUrl = await this.getMcpResourceMetadataUrl(context);
|
|
2553
|
+
const response = this.error(
|
|
2554
|
+
`Forbidden: the access token grants none of the MCP scopes (${import_ai.MCP_OAUTH_SCOPES.join(", ")})`,
|
|
2555
|
+
403
|
|
2556
|
+
);
|
|
2557
|
+
response.headers = {
|
|
2558
|
+
"WWW-Authenticate": `Bearer error="insufficient_scope", scope="${import_ai.MCP_OAUTH_SCOPES.join(" ")}"` + (resourceMetadataUrl ? `, resource_metadata="${resourceMetadataUrl}"` : "")
|
|
2559
|
+
};
|
|
2560
|
+
return { handled: true, response };
|
|
2505
2561
|
}
|
|
2506
2562
|
const webRequest = this.toMcpWebRequest(context.request, body);
|
|
2507
2563
|
if (!webRequest) {
|
|
@@ -2510,7 +2566,13 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
2510
2566
|
const bridge = this.buildMcpBridge(context);
|
|
2511
2567
|
let webRes;
|
|
2512
2568
|
try {
|
|
2513
|
-
webRes = await mcp.handleHttpRequest(webRequest, {
|
|
2569
|
+
webRes = await mcp.handleHttpRequest(webRequest, {
|
|
2570
|
+
bridge,
|
|
2571
|
+
parsedBody: body,
|
|
2572
|
+
// undefined = not scope-limited (API key / session); an array
|
|
2573
|
+
// narrows the registered tool families inside the MCP runtime.
|
|
2574
|
+
...grantedScopes ? { toolOptions: { grantedScopes } } : {}
|
|
2575
|
+
});
|
|
2514
2576
|
} catch (err) {
|
|
2515
2577
|
return { handled: true, response: this.error(err?.message ?? "MCP request failed", 500) };
|
|
2516
2578
|
}
|
|
@@ -2537,9 +2599,28 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
2537
2599
|
}
|
|
2538
2600
|
return { handled: true, response: { status: webRes.status, headers, body: responseBody } };
|
|
2539
2601
|
}
|
|
2540
|
-
/**
|
|
2602
|
+
/**
|
|
2603
|
+
* Whether the MCP HTTP surface is on for this single-env runtime.
|
|
2604
|
+
* Default-on core capability; `OS_MCP_SERVER_ENABLED=false` opts out
|
|
2605
|
+
* (single decision point: `isMcpServerEnabled` in `@objectstack/types`).
|
|
2606
|
+
*/
|
|
2541
2607
|
static isMcpEnabled() {
|
|
2542
|
-
return
|
|
2608
|
+
return (0, import_types3.isMcpServerEnabled)();
|
|
2609
|
+
}
|
|
2610
|
+
/**
|
|
2611
|
+
* Absolute URL of the RFC 9728 protected-resource metadata for the MCP
|
|
2612
|
+
* endpoint, advertised via `WWW-Authenticate` (#2698). `null` when the
|
|
2613
|
+
* OAuth track is off — the auth service owns the decision (AS enabled +
|
|
2614
|
+
* OAuth 2.1 TLS rule), the dispatcher only relays it. Never throws.
|
|
2615
|
+
*/
|
|
2616
|
+
async getMcpResourceMetadataUrl(context) {
|
|
2617
|
+
try {
|
|
2618
|
+
const authService = await this.resolveService("auth", context.environmentId);
|
|
2619
|
+
const url = authService?.getMcpResourceMetadataUrl?.();
|
|
2620
|
+
return typeof url === "string" && url ? url : null;
|
|
2621
|
+
} catch {
|
|
2622
|
+
return null;
|
|
2623
|
+
}
|
|
2543
2624
|
}
|
|
2544
2625
|
/**
|
|
2545
2626
|
* Normalise the inbound request into a Web-standard `Request` for the MCP
|
|
@@ -3197,9 +3278,9 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3197
3278
|
notifications: hasNotification ? `${prefix}/notifications` : void 0,
|
|
3198
3279
|
ai: hasAi ? `${prefix}/ai` : void 0,
|
|
3199
3280
|
i18n: hasI18n ? `${prefix}/i18n` : void 0,
|
|
3200
|
-
// MCP (Streamable HTTP) is
|
|
3201
|
-
//
|
|
3202
|
-
//
|
|
3281
|
+
// MCP (Streamable HTTP) is a default-on core capability —
|
|
3282
|
+
// advertised unless OS_MCP_SERVER_ENABLED=false opts the env
|
|
3283
|
+
// out. The objectui Integrations page reads this.
|
|
3203
3284
|
mcp: _HttpDispatcher.isMcpEnabled() ? `${prefix}/mcp` : void 0
|
|
3204
3285
|
};
|
|
3205
3286
|
const svcAvailable = (route, provider) => ({
|
|
@@ -4489,7 +4570,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4489
4570
|
const ec = context?.executionContext;
|
|
4490
4571
|
const userIdFromAuth = context?.user?.id ?? context?.userId ?? ec?.userId;
|
|
4491
4572
|
if (userIdFromAuth) automationContext.userId = userIdFromAuth;
|
|
4492
|
-
if (Array.isArray(ec?.
|
|
4573
|
+
if (Array.isArray(ec?.positions) && ec.positions.length) automationContext.positions = ec.positions;
|
|
4493
4574
|
if (Array.isArray(ec?.permissions) && ec.permissions.length) automationContext.permissions = ec.permissions;
|
|
4494
4575
|
if (ec?.tenantId) automationContext.tenantId = ec.tenantId;
|
|
4495
4576
|
const result = await automationService.execute(name, automationContext);
|
|
@@ -4797,7 +4878,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4797
4878
|
id: ec.userId,
|
|
4798
4879
|
displayName: ec.userDisplayName ?? ec.userName ?? ec.userId,
|
|
4799
4880
|
email: ec.userEmail,
|
|
4800
|
-
roles: Array.isArray(ec.
|
|
4881
|
+
roles: Array.isArray(ec.positions) ? ec.positions : [],
|
|
4801
4882
|
permissions: Array.isArray(ec.permissions) ? ec.permissions : [],
|
|
4802
4883
|
organizationId: ec.tenantId
|
|
4803
4884
|
} : void 0;
|
|
@@ -4861,7 +4942,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4861
4942
|
if (!svc) {
|
|
4862
4943
|
return { handled: true, response: this.error("Sharing is not configured for this environment", 501) };
|
|
4863
4944
|
}
|
|
4864
|
-
const SYSTEM_CTX = { isSystem: true,
|
|
4945
|
+
const SYSTEM_CTX = { isSystem: true, positions: [], permissions: [] };
|
|
4865
4946
|
const m = method.toUpperCase();
|
|
4866
4947
|
const parts = subPath.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
4867
4948
|
const ec = context.executionContext;
|
|
@@ -5040,7 +5121,13 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5040
5121
|
}
|
|
5041
5122
|
return this.getObjectQLService(context.environmentId);
|
|
5042
5123
|
},
|
|
5043
|
-
request: context.request
|
|
5124
|
+
request: context.request,
|
|
5125
|
+
// OAuth 2.1 access tokens are honoured ONLY on the MCP
|
|
5126
|
+
// surface (#2698): their coarse tool-family scopes are
|
|
5127
|
+
// enforced at MCP tool dispatch, which other routes don't do.
|
|
5128
|
+
// Matches the plain and `/projects/:id`-scoped route forms
|
|
5129
|
+
// (the scoped prefix is stripped only later, below).
|
|
5130
|
+
acceptOAuthAccessToken: /^(?:\/projects\/[^/]+)?\/mcp(?:[/?]|$)/.test(cleanPath)
|
|
5044
5131
|
});
|
|
5045
5132
|
} catch {
|
|
5046
5133
|
}
|
|
@@ -6181,7 +6268,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6181
6268
|
id: userId,
|
|
6182
6269
|
displayName: sessionData?.user?.name ?? sessionData?.user?.email ?? userId,
|
|
6183
6270
|
email: sessionData?.user?.email,
|
|
6184
|
-
|
|
6271
|
+
positions: [],
|
|
6185
6272
|
permissions: [],
|
|
6186
6273
|
organizationId: sessionData?.session?.activeOrganizationId
|
|
6187
6274
|
};
|
|
@@ -6555,7 +6642,7 @@ init_body_runner();
|
|
|
6555
6642
|
// src/index.ts
|
|
6556
6643
|
var import_rest = require("@objectstack/rest");
|
|
6557
6644
|
__reExport(index_exports, require("@objectstack/core"), module.exports);
|
|
6558
|
-
var
|
|
6645
|
+
var import_types4 = require("@objectstack/types");
|
|
6559
6646
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6560
6647
|
0 && (module.exports = {
|
|
6561
6648
|
AppPlugin,
|