@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 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
- ["roles", "role"],
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
- "roles",
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 roles = Array.isArray(artifactBundle?.roles) ? artifactBundle.roles : void 0;
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
- ...roles ? { roles } : {}
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: () => import_types3._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: () => import_types3.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 authz = await (0, import_core3.resolveAuthzContext)({ ql, headers, getSession });
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
- roles: authz.roles,
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
- * - **opt-in**: only served when `OS_MCP_SERVER_ENABLED=true` (single-env
2483
- * runtime). Multi-tenant cloud overrides this gate per env. When off we
2484
- * return 404 so the surface isn't advertised.
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
- return { handled: true, response: this.error("Unauthorized: a valid API key is required", 401) };
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, { bridge, parsedBody: body });
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
- /** Whether the MCP HTTP surface is opted in for this single-env runtime. */
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 typeof process !== "undefined" && process.env?.OS_MCP_SERVER_ENABLED === "true";
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 opt-in per env only advertised
3201
- // when OS_MCP_SERVER_ENABLED=true so the surface isn't exposed
3202
- // by default. The objectui Integrations page reads this.
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?.roles) && ec.roles.length) automationContext.roles = ec.roles;
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 userIdFromAuth = _context?.user?.id ?? _context?.userId ?? "system";
4715
- const userFromAuth = _context?.user ?? { id: userIdFromAuth, name: userIdFromAuth };
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.roles) ? ec.roles : [],
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, roles: [], permissions: [] };
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 ctx2 = { request: req };
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 ctx2 = { request: req };
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
- roles: [],
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 import_types3 = require("@objectstack/types");
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,