@objectstack/runtime 12.6.0 → 14.3.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 +255 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -7
- package/dist/index.d.ts +46 -7
- package/dist/index.js +252 -30
- 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,95 @@ 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
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
/**
|
|
2626
|
+
* `GET /mcp/skill` — the environment-customized portable Agent Skill
|
|
2627
|
+
* (`SKILL.md`), rendered by the MCP service (ADR-0036 Amendment C: ONE
|
|
2628
|
+
* generic skill; only the connection URL is environment-specific).
|
|
2629
|
+
*
|
|
2630
|
+
* Served PUBLIC like `/discovery`: the content is generic agent
|
|
2631
|
+
* instructions plus a URL the caller already knows — no schema, no
|
|
2632
|
+
* tenant data. Gated on the same default-on switch as the `/mcp` route
|
|
2633
|
+
* (404 when opted out, so the surface isn't advertised) and 501 when the
|
|
2634
|
+
* MCP plugin isn't loaded, mirroring `handleMcp`.
|
|
2635
|
+
*/
|
|
2636
|
+
async handleMcpSkill(method, context) {
|
|
2637
|
+
if (!_HttpDispatcher.isMcpEnabled()) {
|
|
2638
|
+
return { handled: true, response: this.error("MCP server is not enabled for this environment", 404) };
|
|
2639
|
+
}
|
|
2640
|
+
if (method !== "GET") {
|
|
2641
|
+
return {
|
|
2642
|
+
handled: true,
|
|
2643
|
+
response: {
|
|
2644
|
+
status: 405,
|
|
2645
|
+
headers: { Allow: "GET" },
|
|
2646
|
+
body: { success: false, error: { message: "Method not allowed \u2014 use GET", code: 405 } }
|
|
2647
|
+
}
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
const mcp = await this.resolveService("mcp", context.environmentId);
|
|
2651
|
+
if (!mcp || typeof mcp.renderSkill !== "function") {
|
|
2652
|
+
return { handled: true, response: this.error("MCP server is not available", 501) };
|
|
2653
|
+
}
|
|
2654
|
+
let mcpUrl;
|
|
2655
|
+
try {
|
|
2656
|
+
const authService = await this.resolveService("auth", context.environmentId);
|
|
2657
|
+
const url = authService?.getMcpResourceUrl?.();
|
|
2658
|
+
if (typeof url === "string" && url) mcpUrl = url;
|
|
2659
|
+
} catch {
|
|
2660
|
+
}
|
|
2661
|
+
if (!mcpUrl) {
|
|
2662
|
+
try {
|
|
2663
|
+
const webReq = this.toMcpWebRequest(context.request, void 0);
|
|
2664
|
+
const host = webReq?.headers.get("host");
|
|
2665
|
+
if (host) {
|
|
2666
|
+
const proto = webReq?.headers.get("x-forwarded-proto") || "http";
|
|
2667
|
+
mcpUrl = `${proto}://${host}/api/v1/mcp`;
|
|
2668
|
+
}
|
|
2669
|
+
} catch {
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
const markdown = mcp.renderSkill({ mcpUrl });
|
|
2673
|
+
return {
|
|
2674
|
+
handled: true,
|
|
2675
|
+
result: {
|
|
2676
|
+
type: "stream",
|
|
2677
|
+
status: 200,
|
|
2678
|
+
contentType: "text/markdown; charset=utf-8",
|
|
2679
|
+
headers: {
|
|
2680
|
+
"content-type": "text/markdown; charset=utf-8",
|
|
2681
|
+
"content-disposition": 'inline; filename="SKILL.md"',
|
|
2682
|
+
// Same reasoning as /discovery (cloud#152): reflects mutable
|
|
2683
|
+
// runtime config (base URL), must never be edge-cached stale.
|
|
2684
|
+
"cache-control": "no-store"
|
|
2685
|
+
},
|
|
2686
|
+
events: (async function* () {
|
|
2687
|
+
yield markdown;
|
|
2688
|
+
})()
|
|
2689
|
+
}
|
|
2690
|
+
};
|
|
2543
2691
|
}
|
|
2544
2692
|
/**
|
|
2545
2693
|
* Normalise the inbound request into a Web-standard `Request` for the MCP
|
|
@@ -3197,9 +3345,9 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3197
3345
|
notifications: hasNotification ? `${prefix}/notifications` : void 0,
|
|
3198
3346
|
ai: hasAi ? `${prefix}/ai` : void 0,
|
|
3199
3347
|
i18n: hasI18n ? `${prefix}/i18n` : void 0,
|
|
3200
|
-
// MCP (Streamable HTTP) is
|
|
3201
|
-
//
|
|
3202
|
-
//
|
|
3348
|
+
// MCP (Streamable HTTP) is a default-on core capability —
|
|
3349
|
+
// advertised unless OS_MCP_SERVER_ENABLED=false opts the env
|
|
3350
|
+
// out. The objectui Integrations page reads this.
|
|
3203
3351
|
mcp: _HttpDispatcher.isMcpEnabled() ? `${prefix}/mcp` : void 0
|
|
3204
3352
|
};
|
|
3205
3353
|
const svcAvailable = (route, provider) => ({
|
|
@@ -3706,6 +3854,56 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3706
3854
|
}
|
|
3707
3855
|
return { handled: false };
|
|
3708
3856
|
}
|
|
3857
|
+
/**
|
|
3858
|
+
* Handle the security admin surface (`/security/...`) — ADR-0090 D5/D9
|
|
3859
|
+
* suggested audience bindings. A package's `isDefault: true` permission
|
|
3860
|
+
* set is an install-time SUGGESTION to bind it to the `everyone` position;
|
|
3861
|
+
* these routes let an admin see and resolve those suggestions. The
|
|
3862
|
+
* `security` service does the real gating (tenant-admin pre-check, and the
|
|
3863
|
+
* confirm write runs under the audience-anchor + delegated-admin gates
|
|
3864
|
+
* with the caller's execution context — never auto-bound, never system).
|
|
3865
|
+
*
|
|
3866
|
+
* Routes:
|
|
3867
|
+
* GET /security/suggested-bindings?status=&packageId= → list (reconciles first)
|
|
3868
|
+
* POST /security/suggested-bindings/:id/confirm → create the anchor binding
|
|
3869
|
+
* POST /security/suggested-bindings/:id/dismiss → decline the suggestion
|
|
3870
|
+
*/
|
|
3871
|
+
async handleSecurity(path, method, _body, query, context) {
|
|
3872
|
+
const service = await this.resolveService("security", context.environmentId);
|
|
3873
|
+
if (!service || typeof service.listAudienceBindingSuggestions !== "function") {
|
|
3874
|
+
return { handled: true, response: this.error("Security service not available", 503) };
|
|
3875
|
+
}
|
|
3876
|
+
const ec = context.executionContext;
|
|
3877
|
+
if (!ec?.userId && !ec?.isSystem) {
|
|
3878
|
+
return { handled: true, response: this.error("Authentication required", 401) };
|
|
3879
|
+
}
|
|
3880
|
+
const m = method.toUpperCase();
|
|
3881
|
+
const parts = path.split("/").filter(Boolean);
|
|
3882
|
+
if (parts[0] !== "suggested-bindings") return { handled: false };
|
|
3883
|
+
try {
|
|
3884
|
+
if (parts.length === 1 && m === "GET") {
|
|
3885
|
+
const status = query?.status ? String(query.status) : void 0;
|
|
3886
|
+
const packageId = query?.packageId ? String(query.packageId) : void 0;
|
|
3887
|
+
const result = await service.listAudienceBindingSuggestions(ec, { status, packageId });
|
|
3888
|
+
return { handled: true, response: this.success(result) };
|
|
3889
|
+
}
|
|
3890
|
+
if (parts.length === 3 && m === "POST") {
|
|
3891
|
+
const id = decodeURIComponent(parts[1]);
|
|
3892
|
+
if (parts[2] === "confirm") {
|
|
3893
|
+
const result = await service.confirmAudienceBindingSuggestion(ec, id);
|
|
3894
|
+
return { handled: true, response: this.success(result) };
|
|
3895
|
+
}
|
|
3896
|
+
if (parts[2] === "dismiss") {
|
|
3897
|
+
const result = await service.dismissAudienceBindingSuggestion(ec, id);
|
|
3898
|
+
return { handled: true, response: this.success(result) };
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
return { handled: false };
|
|
3902
|
+
} catch (err) {
|
|
3903
|
+
const status = typeof err?.statusCode === "number" ? err.statusCode : 500;
|
|
3904
|
+
return { handled: true, response: this.error(err?.message ?? "Security operation failed", status) };
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3709
3907
|
/**
|
|
3710
3908
|
* Handles i18n requests
|
|
3711
3909
|
* path: sub-path after /i18n/
|
|
@@ -4489,7 +4687,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4489
4687
|
const ec = context?.executionContext;
|
|
4490
4688
|
const userIdFromAuth = context?.user?.id ?? context?.userId ?? ec?.userId;
|
|
4491
4689
|
if (userIdFromAuth) automationContext.userId = userIdFromAuth;
|
|
4492
|
-
if (Array.isArray(ec?.
|
|
4690
|
+
if (Array.isArray(ec?.positions) && ec.positions.length) automationContext.positions = ec.positions;
|
|
4493
4691
|
if (Array.isArray(ec?.permissions) && ec.permissions.length) automationContext.permissions = ec.permissions;
|
|
4494
4692
|
if (ec?.tenantId) automationContext.tenantId = ec.tenantId;
|
|
4495
4693
|
const result = await automationService.execute(name, automationContext);
|
|
@@ -4711,8 +4909,16 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4711
4909
|
return Array.isArray(rows) ? rows : rows?.value ?? [];
|
|
4712
4910
|
}
|
|
4713
4911
|
};
|
|
4714
|
-
const
|
|
4715
|
-
const userFromAuth =
|
|
4912
|
+
const ec = _context?.executionContext;
|
|
4913
|
+
const userFromAuth = ec?.userId ? {
|
|
4914
|
+
id: ec.userId,
|
|
4915
|
+
name: ec.userId,
|
|
4916
|
+
email: ec.email,
|
|
4917
|
+
roles: Array.isArray(ec.positions) ? ec.positions : [],
|
|
4918
|
+
positions: Array.isArray(ec.positions) ? ec.positions : [],
|
|
4919
|
+
permissions: Array.isArray(ec.permissions) ? ec.permissions : [],
|
|
4920
|
+
tenantId: ec.tenantId
|
|
4921
|
+
} : { id: "system", name: "system", roles: [], positions: [], permissions: [] };
|
|
4716
4922
|
const actionContext = {
|
|
4717
4923
|
record,
|
|
4718
4924
|
user: userFromAuth,
|
|
@@ -4797,7 +5003,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4797
5003
|
id: ec.userId,
|
|
4798
5004
|
displayName: ec.userDisplayName ?? ec.userName ?? ec.userId,
|
|
4799
5005
|
email: ec.userEmail,
|
|
4800
|
-
roles: Array.isArray(ec.
|
|
5006
|
+
roles: Array.isArray(ec.positions) ? ec.positions : [],
|
|
4801
5007
|
permissions: Array.isArray(ec.permissions) ? ec.permissions : [],
|
|
4802
5008
|
organizationId: ec.tenantId
|
|
4803
5009
|
} : void 0;
|
|
@@ -4861,7 +5067,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4861
5067
|
if (!svc) {
|
|
4862
5068
|
return { handled: true, response: this.error("Sharing is not configured for this environment", 501) };
|
|
4863
5069
|
}
|
|
4864
|
-
const SYSTEM_CTX = { isSystem: true,
|
|
5070
|
+
const SYSTEM_CTX = { isSystem: true, positions: [], permissions: [] };
|
|
4865
5071
|
const m = method.toUpperCase();
|
|
4866
5072
|
const parts = subPath.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
4867
5073
|
const ec = context.executionContext;
|
|
@@ -5040,7 +5246,13 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5040
5246
|
}
|
|
5041
5247
|
return this.getObjectQLService(context.environmentId);
|
|
5042
5248
|
},
|
|
5043
|
-
request: context.request
|
|
5249
|
+
request: context.request,
|
|
5250
|
+
// OAuth 2.1 access tokens are honoured ONLY on the MCP
|
|
5251
|
+
// surface (#2698): their coarse tool-family scopes are
|
|
5252
|
+
// enforced at MCP tool dispatch, which other routes don't do.
|
|
5253
|
+
// Matches the plain and `/projects/:id`-scoped route forms
|
|
5254
|
+
// (the scoped prefix is stripped only later, below).
|
|
5255
|
+
acceptOAuthAccessToken: /^(?:\/projects\/[^/]+)?\/mcp(?:[/?]|$)/.test(cleanPath)
|
|
5044
5256
|
});
|
|
5045
5257
|
} catch {
|
|
5046
5258
|
}
|
|
@@ -5088,6 +5300,9 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5088
5300
|
if (cleanPath.startsWith("/data")) {
|
|
5089
5301
|
return this.handleData(cleanPath.substring(5), method, body, query, context);
|
|
5090
5302
|
}
|
|
5303
|
+
if (cleanPath === "/mcp/skill" || cleanPath.startsWith("/mcp/skill?")) {
|
|
5304
|
+
return this.handleMcpSkill(method, context);
|
|
5305
|
+
}
|
|
5091
5306
|
if (cleanPath === "/mcp" || cleanPath.startsWith("/mcp/") || cleanPath.startsWith("/mcp?")) {
|
|
5092
5307
|
return this.handleMcp(body, context);
|
|
5093
5308
|
}
|
|
@@ -5115,6 +5330,9 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5115
5330
|
if (cleanPath.startsWith("/notifications")) {
|
|
5116
5331
|
return this.handleNotification(cleanPath.substring(14), method, body, query, context);
|
|
5117
5332
|
}
|
|
5333
|
+
if (cleanPath === "/security" || cleanPath.startsWith("/security/")) {
|
|
5334
|
+
return this.handleSecurity(cleanPath.substring(9), method, body, query, context);
|
|
5335
|
+
}
|
|
5118
5336
|
if (cleanPath.startsWith("/packages")) {
|
|
5119
5337
|
return this.handlePackages(cleanPath.substring(9), method, body, query, context);
|
|
5120
5338
|
}
|
|
@@ -5823,6 +6041,14 @@ function createDispatcherPlugin(config = {}) {
|
|
|
5823
6041
|
mountMcp("POST");
|
|
5824
6042
|
mountMcp("GET");
|
|
5825
6043
|
mountMcp("DELETE");
|
|
6044
|
+
server.get(`${prefix}/mcp/skill`, async (req, res) => {
|
|
6045
|
+
try {
|
|
6046
|
+
const result = await dispatcher.dispatch("GET", "/mcp/skill", req.body, req.query, { request: req });
|
|
6047
|
+
sendResult(result, res);
|
|
6048
|
+
} catch (err) {
|
|
6049
|
+
errorResponse(err, res);
|
|
6050
|
+
}
|
|
6051
|
+
});
|
|
5826
6052
|
server.post(`${prefix}/keys`, async (req, res) => {
|
|
5827
6053
|
try {
|
|
5828
6054
|
const result = await dispatcher.dispatch("POST", "/keys", req.body, req.query, { request: req });
|
|
@@ -6127,9 +6353,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6127
6353
|
const registerActionRoutes = (base) => {
|
|
6128
6354
|
server.post(`${base}/actions/:object/:action`, async (req, res) => {
|
|
6129
6355
|
try {
|
|
6130
|
-
const
|
|
6131
|
-
if (req.params?.environmentId) ctx2.environmentId = req.params.environmentId;
|
|
6132
|
-
const result = await dispatcher.handleActions(`/${req.params.object}/${req.params.action}`, "POST", req.body, ctx2);
|
|
6356
|
+
const result = await dispatcher.dispatch("POST", `/actions/${req.params.object}/${req.params.action}`, req.body, req.query, { request: req });
|
|
6133
6357
|
sendResult(result, res);
|
|
6134
6358
|
} catch (err) {
|
|
6135
6359
|
errorResponse(err, res);
|
|
@@ -6137,9 +6361,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6137
6361
|
});
|
|
6138
6362
|
server.post(`${base}/actions/:object/:action/:recordId`, async (req, res) => {
|
|
6139
6363
|
try {
|
|
6140
|
-
const
|
|
6141
|
-
if (req.params?.environmentId) ctx2.environmentId = req.params.environmentId;
|
|
6142
|
-
const result = await dispatcher.handleActions(`/${req.params.object}/${req.params.action}/${req.params.recordId}`, "POST", req.body, ctx2);
|
|
6364
|
+
const result = await dispatcher.dispatch("POST", `/actions/${req.params.object}/${req.params.action}/${req.params.recordId}`, req.body, req.query, { request: req });
|
|
6143
6365
|
sendResult(result, res);
|
|
6144
6366
|
} catch (err) {
|
|
6145
6367
|
errorResponse(err, res);
|
|
@@ -6181,7 +6403,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6181
6403
|
id: userId,
|
|
6182
6404
|
displayName: sessionData?.user?.name ?? sessionData?.user?.email ?? userId,
|
|
6183
6405
|
email: sessionData?.user?.email,
|
|
6184
|
-
|
|
6406
|
+
positions: [],
|
|
6185
6407
|
permissions: [],
|
|
6186
6408
|
organizationId: sessionData?.session?.activeOrganizationId
|
|
6187
6409
|
};
|
|
@@ -6555,7 +6777,7 @@ init_body_runner();
|
|
|
6555
6777
|
// src/index.ts
|
|
6556
6778
|
var import_rest = require("@objectstack/rest");
|
|
6557
6779
|
__reExport(index_exports, require("@objectstack/core"), module.exports);
|
|
6558
|
-
var
|
|
6780
|
+
var import_types4 = require("@objectstack/types");
|
|
6559
6781
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6560
6782
|
0 && (module.exports = {
|
|
6561
6783
|
AppPlugin,
|