@objectstack/runtime 12.5.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 +127 -28
- 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 +124 -25
- package/dist/index.js.map +1 -1
- package/package.json +20 -21
package/dist/index.d.cts
CHANGED
|
@@ -122,15 +122,15 @@ interface StandaloneStackResult {
|
|
|
122
122
|
* App-declared RBAC metadata, surfaced so the CLI (`serve`/`dev`/`start`)
|
|
123
123
|
* can wire it without a host `objectstack.config.ts`. In particular the
|
|
124
124
|
* `serve` command reads `permissions[]` to honour an app-declared default
|
|
125
|
-
* profile (ADR-0056 D7 — `
|
|
126
|
-
* `fallbackPermissionSet`) and reads both `
|
|
125
|
+
* profile (ADR-0056 D7 — `appDefaultPermissionSetName` → SecurityPlugin
|
|
126
|
+
* `fallbackPermissionSet`) and reads both `positions[]` and `permissions[]` to
|
|
127
127
|
* register application org roles with Better-Auth. Without these the
|
|
128
128
|
* artifact-serve path silently fell back to the built-in `member_default`
|
|
129
129
|
* (owner-only), so an `isDefault` profile declared purely in app metadata
|
|
130
130
|
* was ignored under `objectstack dev`.
|
|
131
131
|
*/
|
|
132
132
|
permissions?: any[];
|
|
133
|
-
|
|
133
|
+
positions?: any[];
|
|
134
134
|
}
|
|
135
135
|
declare function createStandaloneStack(config?: StandaloneStackConfig): Promise<StandaloneStackResult>;
|
|
136
136
|
|
|
@@ -1038,9 +1038,10 @@ declare class HttpDispatcher {
|
|
|
1038
1038
|
* Handle an MCP request over the Streamable HTTP transport (`/mcp`).
|
|
1039
1039
|
*
|
|
1040
1040
|
* Gating + auth (fail-closed):
|
|
1041
|
-
* - **
|
|
1042
|
-
* runtime
|
|
1043
|
-
* return 404 so the
|
|
1041
|
+
* - **default-on**: served unless `OS_MCP_SERVER_ENABLED=false` (single-env
|
|
1042
|
+
* runtime; MCP is a core platform capability). Multi-tenant cloud
|
|
1043
|
+
* overrides this gate per env. When opted out we return 404 so the
|
|
1044
|
+
* surface isn't advertised.
|
|
1044
1045
|
* - **auth**: requires a principal already resolved by
|
|
1045
1046
|
* `resolveExecutionContext` (the `sys_api_key` Bearer/header path or a
|
|
1046
1047
|
* session). Anonymous → 401.
|
|
@@ -1051,8 +1052,19 @@ declare class HttpDispatcher {
|
|
|
1051
1052
|
* uses. An external agent can never exceed the key's authority.
|
|
1052
1053
|
*/
|
|
1053
1054
|
handleMcp(body: any, context: HttpProtocolContext): Promise<HttpDispatcherResult>;
|
|
1054
|
-
/**
|
|
1055
|
+
/**
|
|
1056
|
+
* Whether the MCP HTTP surface is on for this single-env runtime.
|
|
1057
|
+
* Default-on core capability; `OS_MCP_SERVER_ENABLED=false` opts out
|
|
1058
|
+
* (single decision point: `isMcpServerEnabled` in `@objectstack/types`).
|
|
1059
|
+
*/
|
|
1055
1060
|
private static isMcpEnabled;
|
|
1061
|
+
/**
|
|
1062
|
+
* Absolute URL of the RFC 9728 protected-resource metadata for the MCP
|
|
1063
|
+
* endpoint, advertised via `WWW-Authenticate` (#2698). `null` when the
|
|
1064
|
+
* OAuth track is off — the auth service owns the decision (AS enabled +
|
|
1065
|
+
* OAuth 2.1 TLS rule), the dispatcher only relays it. Never throws.
|
|
1066
|
+
*/
|
|
1067
|
+
private getMcpResourceMetadataUrl;
|
|
1056
1068
|
/**
|
|
1057
1069
|
* Normalise the inbound request into a Web-standard `Request` for the MCP
|
|
1058
1070
|
* transport. Accepts an already-Web `Request`, or a node/Hono-style req
|
package/dist/index.d.ts
CHANGED
|
@@ -122,15 +122,15 @@ interface StandaloneStackResult {
|
|
|
122
122
|
* App-declared RBAC metadata, surfaced so the CLI (`serve`/`dev`/`start`)
|
|
123
123
|
* can wire it without a host `objectstack.config.ts`. In particular the
|
|
124
124
|
* `serve` command reads `permissions[]` to honour an app-declared default
|
|
125
|
-
* profile (ADR-0056 D7 — `
|
|
126
|
-
* `fallbackPermissionSet`) and reads both `
|
|
125
|
+
* profile (ADR-0056 D7 — `appDefaultPermissionSetName` → SecurityPlugin
|
|
126
|
+
* `fallbackPermissionSet`) and reads both `positions[]` and `permissions[]` to
|
|
127
127
|
* register application org roles with Better-Auth. Without these the
|
|
128
128
|
* artifact-serve path silently fell back to the built-in `member_default`
|
|
129
129
|
* (owner-only), so an `isDefault` profile declared purely in app metadata
|
|
130
130
|
* was ignored under `objectstack dev`.
|
|
131
131
|
*/
|
|
132
132
|
permissions?: any[];
|
|
133
|
-
|
|
133
|
+
positions?: any[];
|
|
134
134
|
}
|
|
135
135
|
declare function createStandaloneStack(config?: StandaloneStackConfig): Promise<StandaloneStackResult>;
|
|
136
136
|
|
|
@@ -1038,9 +1038,10 @@ declare class HttpDispatcher {
|
|
|
1038
1038
|
* Handle an MCP request over the Streamable HTTP transport (`/mcp`).
|
|
1039
1039
|
*
|
|
1040
1040
|
* Gating + auth (fail-closed):
|
|
1041
|
-
* - **
|
|
1042
|
-
* runtime
|
|
1043
|
-
* return 404 so the
|
|
1041
|
+
* - **default-on**: served unless `OS_MCP_SERVER_ENABLED=false` (single-env
|
|
1042
|
+
* runtime; MCP is a core platform capability). Multi-tenant cloud
|
|
1043
|
+
* overrides this gate per env. When opted out we return 404 so the
|
|
1044
|
+
* surface isn't advertised.
|
|
1044
1045
|
* - **auth**: requires a principal already resolved by
|
|
1045
1046
|
* `resolveExecutionContext` (the `sys_api_key` Bearer/header path or a
|
|
1046
1047
|
* session). Anonymous → 401.
|
|
@@ -1051,8 +1052,19 @@ declare class HttpDispatcher {
|
|
|
1051
1052
|
* uses. An external agent can never exceed the key's authority.
|
|
1052
1053
|
*/
|
|
1053
1054
|
handleMcp(body: any, context: HttpProtocolContext): Promise<HttpDispatcherResult>;
|
|
1054
|
-
/**
|
|
1055
|
+
/**
|
|
1056
|
+
* Whether the MCP HTTP surface is on for this single-env runtime.
|
|
1057
|
+
* Default-on core capability; `OS_MCP_SERVER_ENABLED=false` opts out
|
|
1058
|
+
* (single decision point: `isMcpServerEnabled` in `@objectstack/types`).
|
|
1059
|
+
*/
|
|
1055
1060
|
private static isMcpEnabled;
|
|
1061
|
+
/**
|
|
1062
|
+
* Absolute URL of the RFC 9728 protected-resource metadata for the MCP
|
|
1063
|
+
* endpoint, advertised via `WWW-Authenticate` (#2698). `null` when the
|
|
1064
|
+
* OAuth track is off — the auth service owns the decision (AS enabled +
|
|
1065
|
+
* OAuth 2.1 TLS rule), the dispatcher only relays it. Never throws.
|
|
1066
|
+
*/
|
|
1067
|
+
private getMcpResourceMetadataUrl;
|
|
1056
1068
|
/**
|
|
1057
1069
|
* Normalise the inbound request into a Web-standard `Request` for the MCP
|
|
1058
1070
|
* transport. Accepts an already-Web `Request`, or a node/Hono-style req
|
package/dist/index.js
CHANGED
|
@@ -243,8 +243,20 @@ function installApiMethod(vm, parent, method, objectName, ctx, caps, required, o
|
|
|
243
243
|
vm.setProp(parent, method, fn);
|
|
244
244
|
fn.dispose();
|
|
245
245
|
}
|
|
246
|
+
function safeJsonStringify(v) {
|
|
247
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
248
|
+
const json = JSON.stringify(v ?? null, function(_key, value) {
|
|
249
|
+
if (typeof value === "bigint") return value.toString();
|
|
250
|
+
if (typeof value === "object" && value !== null) {
|
|
251
|
+
if (seen.has(value)) return void 0;
|
|
252
|
+
seen.add(value);
|
|
253
|
+
}
|
|
254
|
+
return value;
|
|
255
|
+
});
|
|
256
|
+
return json ?? "null";
|
|
257
|
+
}
|
|
246
258
|
function jsonToHandle(vm, v) {
|
|
247
|
-
const json =
|
|
259
|
+
const json = safeJsonStringify(v);
|
|
248
260
|
const r = vm.evalCode(`(${json})`);
|
|
249
261
|
if (r.error) {
|
|
250
262
|
const msg = vm.dump(r.error);
|
|
@@ -254,7 +266,7 @@ function jsonToHandle(vm, v) {
|
|
|
254
266
|
return r.value;
|
|
255
267
|
}
|
|
256
268
|
function setGlobalJson(vm, name, v) {
|
|
257
|
-
const json =
|
|
269
|
+
const json = safeJsonStringify(v);
|
|
258
270
|
const result = vm.evalCode(`(${json})`);
|
|
259
271
|
if (result.error) {
|
|
260
272
|
result.error.dispose();
|
|
@@ -264,7 +276,7 @@ function setGlobalJson(vm, name, v) {
|
|
|
264
276
|
result.value.dispose();
|
|
265
277
|
}
|
|
266
278
|
function setObjectJson(vm, parent, key, v) {
|
|
267
|
-
const json =
|
|
279
|
+
const json = safeJsonStringify(v);
|
|
268
280
|
const result = vm.evalCode(`(${json})`);
|
|
269
281
|
if (result.error) {
|
|
270
282
|
result.error.dispose();
|
|
@@ -1018,7 +1030,7 @@ var init_app_plugin = __esm({
|
|
|
1018
1030
|
if (typeof metadata?.registerInMemory === "function") {
|
|
1019
1031
|
const securityBundle = this.bundle.manifest ? { ...this.bundle.manifest, ...this.bundle } : this.bundle;
|
|
1020
1032
|
const SECURITY_FIELDS = [
|
|
1021
|
-
["
|
|
1033
|
+
["positions", "position"],
|
|
1022
1034
|
["permissions", "permission"],
|
|
1023
1035
|
["sharingRules", "sharing_rule"],
|
|
1024
1036
|
["policies", "policy"]
|
|
@@ -1384,8 +1396,7 @@ var init_app_plugin = __esm({
|
|
|
1384
1396
|
"skills",
|
|
1385
1397
|
"actions",
|
|
1386
1398
|
"permissions",
|
|
1387
|
-
"
|
|
1388
|
-
"profiles",
|
|
1399
|
+
"positions",
|
|
1389
1400
|
"translations",
|
|
1390
1401
|
"sharingRules",
|
|
1391
1402
|
"ragPipelines",
|
|
@@ -1745,7 +1756,7 @@ async function createStandaloneStack(config) {
|
|
|
1745
1756
|
const objects = Array.isArray(artifactBundle?.objects) ? artifactBundle.objects : void 0;
|
|
1746
1757
|
const manifest = artifactBundle?.manifest;
|
|
1747
1758
|
const permissions = Array.isArray(artifactBundle?.permissions) ? artifactBundle.permissions : void 0;
|
|
1748
|
-
const
|
|
1759
|
+
const positions = Array.isArray(artifactBundle?.positions) ? artifactBundle.positions : void 0;
|
|
1749
1760
|
return {
|
|
1750
1761
|
plugins,
|
|
1751
1762
|
api: {
|
|
@@ -1756,7 +1767,7 @@ async function createStandaloneStack(config) {
|
|
|
1756
1767
|
...objects ? { objects } : {},
|
|
1757
1768
|
...manifest ? { manifest } : {},
|
|
1758
1769
|
...permissions ? { permissions } : {},
|
|
1759
|
-
...
|
|
1770
|
+
...positions ? { positions } : {}
|
|
1760
1771
|
};
|
|
1761
1772
|
}
|
|
1762
1773
|
var StandaloneStackConfigSchema;
|
|
@@ -2073,7 +2084,9 @@ function safeGet(ctx, name) {
|
|
|
2073
2084
|
// src/http-dispatcher.ts
|
|
2074
2085
|
init_package_state_store();
|
|
2075
2086
|
import { getEnv, resolveLocale, evaluateAuthGate, isAuthGateAllowlisted } from "@objectstack/core";
|
|
2087
|
+
import { isMcpServerEnabled } from "@objectstack/types";
|
|
2076
2088
|
import { CoreServiceName } from "@objectstack/spec/system";
|
|
2089
|
+
import { MCP_OAUTH_SCOPES } from "@objectstack/spec/ai";
|
|
2077
2090
|
import { pluralToSingular, PLURAL_TO_SINGULAR } from "@objectstack/spec/shared";
|
|
2078
2091
|
|
|
2079
2092
|
// src/api-exposure.ts
|
|
@@ -2110,6 +2123,12 @@ import {
|
|
|
2110
2123
|
resolveAuthzContext,
|
|
2111
2124
|
resolveLocalizationContext
|
|
2112
2125
|
} from "@objectstack/core";
|
|
2126
|
+
function extractJwtBearer(headers) {
|
|
2127
|
+
const auth = headers.get("authorization");
|
|
2128
|
+
const bearer = auth?.match(/^Bearer\s+(\S+)$/i)?.[1];
|
|
2129
|
+
if (!bearer || bearer.startsWith("osk_")) return void 0;
|
|
2130
|
+
return bearer.split(".").length === 3 ? bearer : void 0;
|
|
2131
|
+
}
|
|
2113
2132
|
function toHeaders(input) {
|
|
2114
2133
|
if (!input) return new Headers();
|
|
2115
2134
|
if (typeof Headers !== "undefined" && input instanceof Headers) return input;
|
|
@@ -2128,6 +2147,22 @@ function toHeaders(input) {
|
|
|
2128
2147
|
async function resolveExecutionContext(opts) {
|
|
2129
2148
|
const headers = toHeaders(opts.request?.headers);
|
|
2130
2149
|
const ql = await opts.getQl();
|
|
2150
|
+
let oauthPrincipal;
|
|
2151
|
+
let oauthBearerPresented = false;
|
|
2152
|
+
if (opts.acceptOAuthAccessToken) {
|
|
2153
|
+
const jwtBearer = extractJwtBearer(headers);
|
|
2154
|
+
if (jwtBearer) {
|
|
2155
|
+
oauthBearerPresented = true;
|
|
2156
|
+
try {
|
|
2157
|
+
const authService = await opts.getService("auth");
|
|
2158
|
+
const verified = await authService?.verifyMcpAccessToken?.(jwtBearer);
|
|
2159
|
+
if (verified?.userId && Array.isArray(verified.scopes)) {
|
|
2160
|
+
oauthPrincipal = verified;
|
|
2161
|
+
}
|
|
2162
|
+
} catch {
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2131
2166
|
const getSession = async (h) => {
|
|
2132
2167
|
try {
|
|
2133
2168
|
const authService = await opts.getService("auth");
|
|
@@ -2138,19 +2173,29 @@ async function resolveExecutionContext(opts) {
|
|
|
2138
2173
|
return void 0;
|
|
2139
2174
|
}
|
|
2140
2175
|
};
|
|
2141
|
-
const
|
|
2176
|
+
const getSessionForProvenance = oauthPrincipal ? async () => ({ user: { id: oauthPrincipal.userId } }) : oauthBearerPresented ? async () => void 0 : getSession;
|
|
2177
|
+
const authz = await resolveAuthzContext({ ql, headers, getSession: getSessionForProvenance });
|
|
2142
2178
|
const ctx = {
|
|
2143
|
-
|
|
2179
|
+
positions: authz.positions,
|
|
2144
2180
|
permissions: authz.permissions,
|
|
2145
2181
|
systemPermissions: authz.systemPermissions,
|
|
2146
2182
|
isSystem: false
|
|
2147
2183
|
};
|
|
2184
|
+
if (authz.userId) {
|
|
2185
|
+
ctx.principalKind = "human";
|
|
2186
|
+
} else {
|
|
2187
|
+
ctx.principalKind = "guest";
|
|
2188
|
+
ctx.positions = ["guest"];
|
|
2189
|
+
}
|
|
2148
2190
|
if (authz.userId) ctx.userId = authz.userId;
|
|
2149
2191
|
if (authz.tenantId) ctx.tenantId = authz.tenantId;
|
|
2150
2192
|
if (authz.email) ctx.email = authz.email;
|
|
2151
2193
|
if (authz.accessToken) ctx.accessToken = authz.accessToken;
|
|
2152
2194
|
if (authz.tabPermissions) ctx.tabPermissions = authz.tabPermissions;
|
|
2153
2195
|
ctx.org_user_ids = authz.org_user_ids;
|
|
2196
|
+
if (oauthPrincipal && ctx.userId === oauthPrincipal.userId) {
|
|
2197
|
+
ctx.oauthScopes = oauthPrincipal.scopes;
|
|
2198
|
+
}
|
|
2154
2199
|
if (authz.userId) {
|
|
2155
2200
|
const settings = await Promise.resolve(opts.getService("settings")).catch(() => void 0);
|
|
2156
2201
|
const localization = await resolveLocalizationContext({
|
|
@@ -2402,9 +2447,10 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
2402
2447
|
* Handle an MCP request over the Streamable HTTP transport (`/mcp`).
|
|
2403
2448
|
*
|
|
2404
2449
|
* Gating + auth (fail-closed):
|
|
2405
|
-
* - **
|
|
2406
|
-
* runtime
|
|
2407
|
-
* return 404 so the
|
|
2450
|
+
* - **default-on**: served unless `OS_MCP_SERVER_ENABLED=false` (single-env
|
|
2451
|
+
* runtime; MCP is a core platform capability). Multi-tenant cloud
|
|
2452
|
+
* overrides this gate per env. When opted out we return 404 so the
|
|
2453
|
+
* surface isn't advertised.
|
|
2408
2454
|
* - **auth**: requires a principal already resolved by
|
|
2409
2455
|
* `resolveExecutionContext` (the `sys_api_key` Bearer/header path or a
|
|
2410
2456
|
* session). Anonymous → 401.
|
|
@@ -2424,7 +2470,29 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
2424
2470
|
}
|
|
2425
2471
|
const ec = context.executionContext;
|
|
2426
2472
|
if (!ec || !ec.userId && !ec.isSystem) {
|
|
2427
|
-
|
|
2473
|
+
const resourceMetadataUrl = await this.getMcpResourceMetadataUrl(context);
|
|
2474
|
+
const response = this.error(
|
|
2475
|
+
resourceMetadataUrl ? "Unauthorized: a valid OAuth access token or API key is required" : "Unauthorized: a valid API key is required",
|
|
2476
|
+
401
|
|
2477
|
+
);
|
|
2478
|
+
if (resourceMetadataUrl) {
|
|
2479
|
+
response.headers = {
|
|
2480
|
+
"WWW-Authenticate": `Bearer realm="ObjectStack MCP", resource_metadata="${resourceMetadataUrl}"`
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
return { handled: true, response };
|
|
2484
|
+
}
|
|
2485
|
+
const grantedScopes = Array.isArray(ec.oauthScopes) ? ec.oauthScopes : void 0;
|
|
2486
|
+
if (grantedScopes && !grantedScopes.some((s) => MCP_OAUTH_SCOPES.includes(s))) {
|
|
2487
|
+
const resourceMetadataUrl = await this.getMcpResourceMetadataUrl(context);
|
|
2488
|
+
const response = this.error(
|
|
2489
|
+
`Forbidden: the access token grants none of the MCP scopes (${MCP_OAUTH_SCOPES.join(", ")})`,
|
|
2490
|
+
403
|
|
2491
|
+
);
|
|
2492
|
+
response.headers = {
|
|
2493
|
+
"WWW-Authenticate": `Bearer error="insufficient_scope", scope="${MCP_OAUTH_SCOPES.join(" ")}"` + (resourceMetadataUrl ? `, resource_metadata="${resourceMetadataUrl}"` : "")
|
|
2494
|
+
};
|
|
2495
|
+
return { handled: true, response };
|
|
2428
2496
|
}
|
|
2429
2497
|
const webRequest = this.toMcpWebRequest(context.request, body);
|
|
2430
2498
|
if (!webRequest) {
|
|
@@ -2433,7 +2501,13 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
2433
2501
|
const bridge = this.buildMcpBridge(context);
|
|
2434
2502
|
let webRes;
|
|
2435
2503
|
try {
|
|
2436
|
-
webRes = await mcp.handleHttpRequest(webRequest, {
|
|
2504
|
+
webRes = await mcp.handleHttpRequest(webRequest, {
|
|
2505
|
+
bridge,
|
|
2506
|
+
parsedBody: body,
|
|
2507
|
+
// undefined = not scope-limited (API key / session); an array
|
|
2508
|
+
// narrows the registered tool families inside the MCP runtime.
|
|
2509
|
+
...grantedScopes ? { toolOptions: { grantedScopes } } : {}
|
|
2510
|
+
});
|
|
2437
2511
|
} catch (err) {
|
|
2438
2512
|
return { handled: true, response: this.error(err?.message ?? "MCP request failed", 500) };
|
|
2439
2513
|
}
|
|
@@ -2460,9 +2534,28 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
2460
2534
|
}
|
|
2461
2535
|
return { handled: true, response: { status: webRes.status, headers, body: responseBody } };
|
|
2462
2536
|
}
|
|
2463
|
-
/**
|
|
2537
|
+
/**
|
|
2538
|
+
* Whether the MCP HTTP surface is on for this single-env runtime.
|
|
2539
|
+
* Default-on core capability; `OS_MCP_SERVER_ENABLED=false` opts out
|
|
2540
|
+
* (single decision point: `isMcpServerEnabled` in `@objectstack/types`).
|
|
2541
|
+
*/
|
|
2464
2542
|
static isMcpEnabled() {
|
|
2465
|
-
return
|
|
2543
|
+
return isMcpServerEnabled();
|
|
2544
|
+
}
|
|
2545
|
+
/**
|
|
2546
|
+
* Absolute URL of the RFC 9728 protected-resource metadata for the MCP
|
|
2547
|
+
* endpoint, advertised via `WWW-Authenticate` (#2698). `null` when the
|
|
2548
|
+
* OAuth track is off — the auth service owns the decision (AS enabled +
|
|
2549
|
+
* OAuth 2.1 TLS rule), the dispatcher only relays it. Never throws.
|
|
2550
|
+
*/
|
|
2551
|
+
async getMcpResourceMetadataUrl(context) {
|
|
2552
|
+
try {
|
|
2553
|
+
const authService = await this.resolveService("auth", context.environmentId);
|
|
2554
|
+
const url = authService?.getMcpResourceMetadataUrl?.();
|
|
2555
|
+
return typeof url === "string" && url ? url : null;
|
|
2556
|
+
} catch {
|
|
2557
|
+
return null;
|
|
2558
|
+
}
|
|
2466
2559
|
}
|
|
2467
2560
|
/**
|
|
2468
2561
|
* Normalise the inbound request into a Web-standard `Request` for the MCP
|
|
@@ -3120,9 +3213,9 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3120
3213
|
notifications: hasNotification ? `${prefix}/notifications` : void 0,
|
|
3121
3214
|
ai: hasAi ? `${prefix}/ai` : void 0,
|
|
3122
3215
|
i18n: hasI18n ? `${prefix}/i18n` : void 0,
|
|
3123
|
-
// MCP (Streamable HTTP) is
|
|
3124
|
-
//
|
|
3125
|
-
//
|
|
3216
|
+
// MCP (Streamable HTTP) is a default-on core capability —
|
|
3217
|
+
// advertised unless OS_MCP_SERVER_ENABLED=false opts the env
|
|
3218
|
+
// out. The objectui Integrations page reads this.
|
|
3126
3219
|
mcp: _HttpDispatcher.isMcpEnabled() ? `${prefix}/mcp` : void 0
|
|
3127
3220
|
};
|
|
3128
3221
|
const svcAvailable = (route, provider) => ({
|
|
@@ -4412,7 +4505,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4412
4505
|
const ec = context?.executionContext;
|
|
4413
4506
|
const userIdFromAuth = context?.user?.id ?? context?.userId ?? ec?.userId;
|
|
4414
4507
|
if (userIdFromAuth) automationContext.userId = userIdFromAuth;
|
|
4415
|
-
if (Array.isArray(ec?.
|
|
4508
|
+
if (Array.isArray(ec?.positions) && ec.positions.length) automationContext.positions = ec.positions;
|
|
4416
4509
|
if (Array.isArray(ec?.permissions) && ec.permissions.length) automationContext.permissions = ec.permissions;
|
|
4417
4510
|
if (ec?.tenantId) automationContext.tenantId = ec.tenantId;
|
|
4418
4511
|
const result = await automationService.execute(name, automationContext);
|
|
@@ -4720,7 +4813,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4720
4813
|
id: ec.userId,
|
|
4721
4814
|
displayName: ec.userDisplayName ?? ec.userName ?? ec.userId,
|
|
4722
4815
|
email: ec.userEmail,
|
|
4723
|
-
roles: Array.isArray(ec.
|
|
4816
|
+
roles: Array.isArray(ec.positions) ? ec.positions : [],
|
|
4724
4817
|
permissions: Array.isArray(ec.permissions) ? ec.permissions : [],
|
|
4725
4818
|
organizationId: ec.tenantId
|
|
4726
4819
|
} : void 0;
|
|
@@ -4784,7 +4877,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4784
4877
|
if (!svc) {
|
|
4785
4878
|
return { handled: true, response: this.error("Sharing is not configured for this environment", 501) };
|
|
4786
4879
|
}
|
|
4787
|
-
const SYSTEM_CTX = { isSystem: true,
|
|
4880
|
+
const SYSTEM_CTX = { isSystem: true, positions: [], permissions: [] };
|
|
4788
4881
|
const m = method.toUpperCase();
|
|
4789
4882
|
const parts = subPath.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
4790
4883
|
const ec = context.executionContext;
|
|
@@ -4963,7 +5056,13 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4963
5056
|
}
|
|
4964
5057
|
return this.getObjectQLService(context.environmentId);
|
|
4965
5058
|
},
|
|
4966
|
-
request: context.request
|
|
5059
|
+
request: context.request,
|
|
5060
|
+
// OAuth 2.1 access tokens are honoured ONLY on the MCP
|
|
5061
|
+
// surface (#2698): their coarse tool-family scopes are
|
|
5062
|
+
// enforced at MCP tool dispatch, which other routes don't do.
|
|
5063
|
+
// Matches the plain and `/projects/:id`-scoped route forms
|
|
5064
|
+
// (the scoped prefix is stripped only later, below).
|
|
5065
|
+
acceptOAuthAccessToken: /^(?:\/projects\/[^/]+)?\/mcp(?:[/?]|$)/.test(cleanPath)
|
|
4967
5066
|
});
|
|
4968
5067
|
} catch {
|
|
4969
5068
|
}
|
|
@@ -6114,7 +6213,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6114
6213
|
id: userId,
|
|
6115
6214
|
displayName: sessionData?.user?.name ?? sessionData?.user?.email ?? userId,
|
|
6116
6215
|
email: sessionData?.user?.email,
|
|
6117
|
-
|
|
6216
|
+
positions: [],
|
|
6118
6217
|
permissions: [],
|
|
6119
6218
|
organizationId: sessionData?.session?.activeOrganizationId
|
|
6120
6219
|
};
|