@objectstack/runtime 13.0.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 +143 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +143 -8
- package/dist/index.js.map +1 -1
- package/package.json +20 -20
package/dist/index.cjs
CHANGED
|
@@ -2622,6 +2622,73 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
2622
2622
|
return null;
|
|
2623
2623
|
}
|
|
2624
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
|
+
};
|
|
2691
|
+
}
|
|
2625
2692
|
/**
|
|
2626
2693
|
* Normalise the inbound request into a Web-standard `Request` for the MCP
|
|
2627
2694
|
* transport. Accepts an already-Web `Request`, or a node/Hono-style req
|
|
@@ -3787,6 +3854,56 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3787
3854
|
}
|
|
3788
3855
|
return { handled: false };
|
|
3789
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
|
+
}
|
|
3790
3907
|
/**
|
|
3791
3908
|
* Handles i18n requests
|
|
3792
3909
|
* path: sub-path after /i18n/
|
|
@@ -4792,8 +4909,16 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4792
4909
|
return Array.isArray(rows) ? rows : rows?.value ?? [];
|
|
4793
4910
|
}
|
|
4794
4911
|
};
|
|
4795
|
-
const
|
|
4796
|
-
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: [] };
|
|
4797
4922
|
const actionContext = {
|
|
4798
4923
|
record,
|
|
4799
4924
|
user: userFromAuth,
|
|
@@ -5175,6 +5300,9 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5175
5300
|
if (cleanPath.startsWith("/data")) {
|
|
5176
5301
|
return this.handleData(cleanPath.substring(5), method, body, query, context);
|
|
5177
5302
|
}
|
|
5303
|
+
if (cleanPath === "/mcp/skill" || cleanPath.startsWith("/mcp/skill?")) {
|
|
5304
|
+
return this.handleMcpSkill(method, context);
|
|
5305
|
+
}
|
|
5178
5306
|
if (cleanPath === "/mcp" || cleanPath.startsWith("/mcp/") || cleanPath.startsWith("/mcp?")) {
|
|
5179
5307
|
return this.handleMcp(body, context);
|
|
5180
5308
|
}
|
|
@@ -5202,6 +5330,9 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5202
5330
|
if (cleanPath.startsWith("/notifications")) {
|
|
5203
5331
|
return this.handleNotification(cleanPath.substring(14), method, body, query, context);
|
|
5204
5332
|
}
|
|
5333
|
+
if (cleanPath === "/security" || cleanPath.startsWith("/security/")) {
|
|
5334
|
+
return this.handleSecurity(cleanPath.substring(9), method, body, query, context);
|
|
5335
|
+
}
|
|
5205
5336
|
if (cleanPath.startsWith("/packages")) {
|
|
5206
5337
|
return this.handlePackages(cleanPath.substring(9), method, body, query, context);
|
|
5207
5338
|
}
|
|
@@ -5910,6 +6041,14 @@ function createDispatcherPlugin(config = {}) {
|
|
|
5910
6041
|
mountMcp("POST");
|
|
5911
6042
|
mountMcp("GET");
|
|
5912
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
|
+
});
|
|
5913
6052
|
server.post(`${prefix}/keys`, async (req, res) => {
|
|
5914
6053
|
try {
|
|
5915
6054
|
const result = await dispatcher.dispatch("POST", "/keys", req.body, req.query, { request: req });
|
|
@@ -6214,9 +6353,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6214
6353
|
const registerActionRoutes = (base) => {
|
|
6215
6354
|
server.post(`${base}/actions/:object/:action`, async (req, res) => {
|
|
6216
6355
|
try {
|
|
6217
|
-
const
|
|
6218
|
-
if (req.params?.environmentId) ctx2.environmentId = req.params.environmentId;
|
|
6219
|
-
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 });
|
|
6220
6357
|
sendResult(result, res);
|
|
6221
6358
|
} catch (err) {
|
|
6222
6359
|
errorResponse(err, res);
|
|
@@ -6224,9 +6361,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6224
6361
|
});
|
|
6225
6362
|
server.post(`${base}/actions/:object/:action/:recordId`, async (req, res) => {
|
|
6226
6363
|
try {
|
|
6227
|
-
const
|
|
6228
|
-
if (req.params?.environmentId) ctx2.environmentId = req.params.environmentId;
|
|
6229
|
-
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 });
|
|
6230
6365
|
sendResult(result, res);
|
|
6231
6366
|
} catch (err) {
|
|
6232
6367
|
errorResponse(err, res);
|