@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.d.cts
CHANGED
|
@@ -1065,6 +1065,18 @@ declare class HttpDispatcher {
|
|
|
1065
1065
|
* OAuth 2.1 TLS rule), the dispatcher only relays it. Never throws.
|
|
1066
1066
|
*/
|
|
1067
1067
|
private getMcpResourceMetadataUrl;
|
|
1068
|
+
/**
|
|
1069
|
+
* `GET /mcp/skill` — the environment-customized portable Agent Skill
|
|
1070
|
+
* (`SKILL.md`), rendered by the MCP service (ADR-0036 Amendment C: ONE
|
|
1071
|
+
* generic skill; only the connection URL is environment-specific).
|
|
1072
|
+
*
|
|
1073
|
+
* Served PUBLIC like `/discovery`: the content is generic agent
|
|
1074
|
+
* instructions plus a URL the caller already knows — no schema, no
|
|
1075
|
+
* tenant data. Gated on the same default-on switch as the `/mcp` route
|
|
1076
|
+
* (404 when opted out, so the surface isn't advertised) and 501 when the
|
|
1077
|
+
* MCP plugin isn't loaded, mirroring `handleMcp`.
|
|
1078
|
+
*/
|
|
1079
|
+
handleMcpSkill(method: string, context: HttpProtocolContext): Promise<HttpDispatcherResult>;
|
|
1068
1080
|
/**
|
|
1069
1081
|
* Normalise the inbound request into a Web-standard `Request` for the MCP
|
|
1070
1082
|
* transport. Accepts an already-Web `Request`, or a node/Hono-style req
|
|
@@ -1501,6 +1513,21 @@ declare class HttpDispatcher {
|
|
|
1501
1513
|
* POST /read/all → markAllRead
|
|
1502
1514
|
*/
|
|
1503
1515
|
handleNotification(path: string, method: string, body: any, query: any, context: HttpProtocolContext): Promise<HttpDispatcherResult>;
|
|
1516
|
+
/**
|
|
1517
|
+
* Handle the security admin surface (`/security/...`) — ADR-0090 D5/D9
|
|
1518
|
+
* suggested audience bindings. A package's `isDefault: true` permission
|
|
1519
|
+
* set is an install-time SUGGESTION to bind it to the `everyone` position;
|
|
1520
|
+
* these routes let an admin see and resolve those suggestions. The
|
|
1521
|
+
* `security` service does the real gating (tenant-admin pre-check, and the
|
|
1522
|
+
* confirm write runs under the audience-anchor + delegated-admin gates
|
|
1523
|
+
* with the caller's execution context — never auto-bound, never system).
|
|
1524
|
+
*
|
|
1525
|
+
* Routes:
|
|
1526
|
+
* GET /security/suggested-bindings?status=&packageId= → list (reconciles first)
|
|
1527
|
+
* POST /security/suggested-bindings/:id/confirm → create the anchor binding
|
|
1528
|
+
* POST /security/suggested-bindings/:id/dismiss → decline the suggestion
|
|
1529
|
+
*/
|
|
1530
|
+
handleSecurity(path: string, method: string, _body: any, query: any, context: HttpProtocolContext): Promise<HttpDispatcherResult>;
|
|
1504
1531
|
/**
|
|
1505
1532
|
* Handles i18n requests
|
|
1506
1533
|
* path: sub-path after /i18n/
|
package/dist/index.d.ts
CHANGED
|
@@ -1065,6 +1065,18 @@ declare class HttpDispatcher {
|
|
|
1065
1065
|
* OAuth 2.1 TLS rule), the dispatcher only relays it. Never throws.
|
|
1066
1066
|
*/
|
|
1067
1067
|
private getMcpResourceMetadataUrl;
|
|
1068
|
+
/**
|
|
1069
|
+
* `GET /mcp/skill` — the environment-customized portable Agent Skill
|
|
1070
|
+
* (`SKILL.md`), rendered by the MCP service (ADR-0036 Amendment C: ONE
|
|
1071
|
+
* generic skill; only the connection URL is environment-specific).
|
|
1072
|
+
*
|
|
1073
|
+
* Served PUBLIC like `/discovery`: the content is generic agent
|
|
1074
|
+
* instructions plus a URL the caller already knows — no schema, no
|
|
1075
|
+
* tenant data. Gated on the same default-on switch as the `/mcp` route
|
|
1076
|
+
* (404 when opted out, so the surface isn't advertised) and 501 when the
|
|
1077
|
+
* MCP plugin isn't loaded, mirroring `handleMcp`.
|
|
1078
|
+
*/
|
|
1079
|
+
handleMcpSkill(method: string, context: HttpProtocolContext): Promise<HttpDispatcherResult>;
|
|
1068
1080
|
/**
|
|
1069
1081
|
* Normalise the inbound request into a Web-standard `Request` for the MCP
|
|
1070
1082
|
* transport. Accepts an already-Web `Request`, or a node/Hono-style req
|
|
@@ -1501,6 +1513,21 @@ declare class HttpDispatcher {
|
|
|
1501
1513
|
* POST /read/all → markAllRead
|
|
1502
1514
|
*/
|
|
1503
1515
|
handleNotification(path: string, method: string, body: any, query: any, context: HttpProtocolContext): Promise<HttpDispatcherResult>;
|
|
1516
|
+
/**
|
|
1517
|
+
* Handle the security admin surface (`/security/...`) — ADR-0090 D5/D9
|
|
1518
|
+
* suggested audience bindings. A package's `isDefault: true` permission
|
|
1519
|
+
* set is an install-time SUGGESTION to bind it to the `everyone` position;
|
|
1520
|
+
* these routes let an admin see and resolve those suggestions. The
|
|
1521
|
+
* `security` service does the real gating (tenant-admin pre-check, and the
|
|
1522
|
+
* confirm write runs under the audience-anchor + delegated-admin gates
|
|
1523
|
+
* with the caller's execution context — never auto-bound, never system).
|
|
1524
|
+
*
|
|
1525
|
+
* Routes:
|
|
1526
|
+
* GET /security/suggested-bindings?status=&packageId= → list (reconciles first)
|
|
1527
|
+
* POST /security/suggested-bindings/:id/confirm → create the anchor binding
|
|
1528
|
+
* POST /security/suggested-bindings/:id/dismiss → decline the suggestion
|
|
1529
|
+
*/
|
|
1530
|
+
handleSecurity(path: string, method: string, _body: any, query: any, context: HttpProtocolContext): Promise<HttpDispatcherResult>;
|
|
1504
1531
|
/**
|
|
1505
1532
|
* Handles i18n requests
|
|
1506
1533
|
* path: sub-path after /i18n/
|
package/dist/index.js
CHANGED
|
@@ -2557,6 +2557,73 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
2557
2557
|
return null;
|
|
2558
2558
|
}
|
|
2559
2559
|
}
|
|
2560
|
+
/**
|
|
2561
|
+
* `GET /mcp/skill` — the environment-customized portable Agent Skill
|
|
2562
|
+
* (`SKILL.md`), rendered by the MCP service (ADR-0036 Amendment C: ONE
|
|
2563
|
+
* generic skill; only the connection URL is environment-specific).
|
|
2564
|
+
*
|
|
2565
|
+
* Served PUBLIC like `/discovery`: the content is generic agent
|
|
2566
|
+
* instructions plus a URL the caller already knows — no schema, no
|
|
2567
|
+
* tenant data. Gated on the same default-on switch as the `/mcp` route
|
|
2568
|
+
* (404 when opted out, so the surface isn't advertised) and 501 when the
|
|
2569
|
+
* MCP plugin isn't loaded, mirroring `handleMcp`.
|
|
2570
|
+
*/
|
|
2571
|
+
async handleMcpSkill(method, context) {
|
|
2572
|
+
if (!_HttpDispatcher.isMcpEnabled()) {
|
|
2573
|
+
return { handled: true, response: this.error("MCP server is not enabled for this environment", 404) };
|
|
2574
|
+
}
|
|
2575
|
+
if (method !== "GET") {
|
|
2576
|
+
return {
|
|
2577
|
+
handled: true,
|
|
2578
|
+
response: {
|
|
2579
|
+
status: 405,
|
|
2580
|
+
headers: { Allow: "GET" },
|
|
2581
|
+
body: { success: false, error: { message: "Method not allowed \u2014 use GET", code: 405 } }
|
|
2582
|
+
}
|
|
2583
|
+
};
|
|
2584
|
+
}
|
|
2585
|
+
const mcp = await this.resolveService("mcp", context.environmentId);
|
|
2586
|
+
if (!mcp || typeof mcp.renderSkill !== "function") {
|
|
2587
|
+
return { handled: true, response: this.error("MCP server is not available", 501) };
|
|
2588
|
+
}
|
|
2589
|
+
let mcpUrl;
|
|
2590
|
+
try {
|
|
2591
|
+
const authService = await this.resolveService("auth", context.environmentId);
|
|
2592
|
+
const url = authService?.getMcpResourceUrl?.();
|
|
2593
|
+
if (typeof url === "string" && url) mcpUrl = url;
|
|
2594
|
+
} catch {
|
|
2595
|
+
}
|
|
2596
|
+
if (!mcpUrl) {
|
|
2597
|
+
try {
|
|
2598
|
+
const webReq = this.toMcpWebRequest(context.request, void 0);
|
|
2599
|
+
const host = webReq?.headers.get("host");
|
|
2600
|
+
if (host) {
|
|
2601
|
+
const proto = webReq?.headers.get("x-forwarded-proto") || "http";
|
|
2602
|
+
mcpUrl = `${proto}://${host}/api/v1/mcp`;
|
|
2603
|
+
}
|
|
2604
|
+
} catch {
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
const markdown = mcp.renderSkill({ mcpUrl });
|
|
2608
|
+
return {
|
|
2609
|
+
handled: true,
|
|
2610
|
+
result: {
|
|
2611
|
+
type: "stream",
|
|
2612
|
+
status: 200,
|
|
2613
|
+
contentType: "text/markdown; charset=utf-8",
|
|
2614
|
+
headers: {
|
|
2615
|
+
"content-type": "text/markdown; charset=utf-8",
|
|
2616
|
+
"content-disposition": 'inline; filename="SKILL.md"',
|
|
2617
|
+
// Same reasoning as /discovery (cloud#152): reflects mutable
|
|
2618
|
+
// runtime config (base URL), must never be edge-cached stale.
|
|
2619
|
+
"cache-control": "no-store"
|
|
2620
|
+
},
|
|
2621
|
+
events: (async function* () {
|
|
2622
|
+
yield markdown;
|
|
2623
|
+
})()
|
|
2624
|
+
}
|
|
2625
|
+
};
|
|
2626
|
+
}
|
|
2560
2627
|
/**
|
|
2561
2628
|
* Normalise the inbound request into a Web-standard `Request` for the MCP
|
|
2562
2629
|
* transport. Accepts an already-Web `Request`, or a node/Hono-style req
|
|
@@ -3722,6 +3789,56 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3722
3789
|
}
|
|
3723
3790
|
return { handled: false };
|
|
3724
3791
|
}
|
|
3792
|
+
/**
|
|
3793
|
+
* Handle the security admin surface (`/security/...`) — ADR-0090 D5/D9
|
|
3794
|
+
* suggested audience bindings. A package's `isDefault: true` permission
|
|
3795
|
+
* set is an install-time SUGGESTION to bind it to the `everyone` position;
|
|
3796
|
+
* these routes let an admin see and resolve those suggestions. The
|
|
3797
|
+
* `security` service does the real gating (tenant-admin pre-check, and the
|
|
3798
|
+
* confirm write runs under the audience-anchor + delegated-admin gates
|
|
3799
|
+
* with the caller's execution context — never auto-bound, never system).
|
|
3800
|
+
*
|
|
3801
|
+
* Routes:
|
|
3802
|
+
* GET /security/suggested-bindings?status=&packageId= → list (reconciles first)
|
|
3803
|
+
* POST /security/suggested-bindings/:id/confirm → create the anchor binding
|
|
3804
|
+
* POST /security/suggested-bindings/:id/dismiss → decline the suggestion
|
|
3805
|
+
*/
|
|
3806
|
+
async handleSecurity(path, method, _body, query, context) {
|
|
3807
|
+
const service = await this.resolveService("security", context.environmentId);
|
|
3808
|
+
if (!service || typeof service.listAudienceBindingSuggestions !== "function") {
|
|
3809
|
+
return { handled: true, response: this.error("Security service not available", 503) };
|
|
3810
|
+
}
|
|
3811
|
+
const ec = context.executionContext;
|
|
3812
|
+
if (!ec?.userId && !ec?.isSystem) {
|
|
3813
|
+
return { handled: true, response: this.error("Authentication required", 401) };
|
|
3814
|
+
}
|
|
3815
|
+
const m = method.toUpperCase();
|
|
3816
|
+
const parts = path.split("/").filter(Boolean);
|
|
3817
|
+
if (parts[0] !== "suggested-bindings") return { handled: false };
|
|
3818
|
+
try {
|
|
3819
|
+
if (parts.length === 1 && m === "GET") {
|
|
3820
|
+
const status = query?.status ? String(query.status) : void 0;
|
|
3821
|
+
const packageId = query?.packageId ? String(query.packageId) : void 0;
|
|
3822
|
+
const result = await service.listAudienceBindingSuggestions(ec, { status, packageId });
|
|
3823
|
+
return { handled: true, response: this.success(result) };
|
|
3824
|
+
}
|
|
3825
|
+
if (parts.length === 3 && m === "POST") {
|
|
3826
|
+
const id = decodeURIComponent(parts[1]);
|
|
3827
|
+
if (parts[2] === "confirm") {
|
|
3828
|
+
const result = await service.confirmAudienceBindingSuggestion(ec, id);
|
|
3829
|
+
return { handled: true, response: this.success(result) };
|
|
3830
|
+
}
|
|
3831
|
+
if (parts[2] === "dismiss") {
|
|
3832
|
+
const result = await service.dismissAudienceBindingSuggestion(ec, id);
|
|
3833
|
+
return { handled: true, response: this.success(result) };
|
|
3834
|
+
}
|
|
3835
|
+
}
|
|
3836
|
+
return { handled: false };
|
|
3837
|
+
} catch (err) {
|
|
3838
|
+
const status = typeof err?.statusCode === "number" ? err.statusCode : 500;
|
|
3839
|
+
return { handled: true, response: this.error(err?.message ?? "Security operation failed", status) };
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3725
3842
|
/**
|
|
3726
3843
|
* Handles i18n requests
|
|
3727
3844
|
* path: sub-path after /i18n/
|
|
@@ -4727,8 +4844,16 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4727
4844
|
return Array.isArray(rows) ? rows : rows?.value ?? [];
|
|
4728
4845
|
}
|
|
4729
4846
|
};
|
|
4730
|
-
const
|
|
4731
|
-
const userFromAuth =
|
|
4847
|
+
const ec = _context?.executionContext;
|
|
4848
|
+
const userFromAuth = ec?.userId ? {
|
|
4849
|
+
id: ec.userId,
|
|
4850
|
+
name: ec.userId,
|
|
4851
|
+
email: ec.email,
|
|
4852
|
+
roles: Array.isArray(ec.positions) ? ec.positions : [],
|
|
4853
|
+
positions: Array.isArray(ec.positions) ? ec.positions : [],
|
|
4854
|
+
permissions: Array.isArray(ec.permissions) ? ec.permissions : [],
|
|
4855
|
+
tenantId: ec.tenantId
|
|
4856
|
+
} : { id: "system", name: "system", roles: [], positions: [], permissions: [] };
|
|
4732
4857
|
const actionContext = {
|
|
4733
4858
|
record,
|
|
4734
4859
|
user: userFromAuth,
|
|
@@ -5110,6 +5235,9 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5110
5235
|
if (cleanPath.startsWith("/data")) {
|
|
5111
5236
|
return this.handleData(cleanPath.substring(5), method, body, query, context);
|
|
5112
5237
|
}
|
|
5238
|
+
if (cleanPath === "/mcp/skill" || cleanPath.startsWith("/mcp/skill?")) {
|
|
5239
|
+
return this.handleMcpSkill(method, context);
|
|
5240
|
+
}
|
|
5113
5241
|
if (cleanPath === "/mcp" || cleanPath.startsWith("/mcp/") || cleanPath.startsWith("/mcp?")) {
|
|
5114
5242
|
return this.handleMcp(body, context);
|
|
5115
5243
|
}
|
|
@@ -5137,6 +5265,9 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5137
5265
|
if (cleanPath.startsWith("/notifications")) {
|
|
5138
5266
|
return this.handleNotification(cleanPath.substring(14), method, body, query, context);
|
|
5139
5267
|
}
|
|
5268
|
+
if (cleanPath === "/security" || cleanPath.startsWith("/security/")) {
|
|
5269
|
+
return this.handleSecurity(cleanPath.substring(9), method, body, query, context);
|
|
5270
|
+
}
|
|
5140
5271
|
if (cleanPath.startsWith("/packages")) {
|
|
5141
5272
|
return this.handlePackages(cleanPath.substring(9), method, body, query, context);
|
|
5142
5273
|
}
|
|
@@ -5855,6 +5986,14 @@ function createDispatcherPlugin(config = {}) {
|
|
|
5855
5986
|
mountMcp("POST");
|
|
5856
5987
|
mountMcp("GET");
|
|
5857
5988
|
mountMcp("DELETE");
|
|
5989
|
+
server.get(`${prefix}/mcp/skill`, async (req, res) => {
|
|
5990
|
+
try {
|
|
5991
|
+
const result = await dispatcher.dispatch("GET", "/mcp/skill", req.body, req.query, { request: req });
|
|
5992
|
+
sendResult(result, res);
|
|
5993
|
+
} catch (err) {
|
|
5994
|
+
errorResponse(err, res);
|
|
5995
|
+
}
|
|
5996
|
+
});
|
|
5858
5997
|
server.post(`${prefix}/keys`, async (req, res) => {
|
|
5859
5998
|
try {
|
|
5860
5999
|
const result = await dispatcher.dispatch("POST", "/keys", req.body, req.query, { request: req });
|
|
@@ -6159,9 +6298,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6159
6298
|
const registerActionRoutes = (base) => {
|
|
6160
6299
|
server.post(`${base}/actions/:object/:action`, async (req, res) => {
|
|
6161
6300
|
try {
|
|
6162
|
-
const
|
|
6163
|
-
if (req.params?.environmentId) ctx2.environmentId = req.params.environmentId;
|
|
6164
|
-
const result = await dispatcher.handleActions(`/${req.params.object}/${req.params.action}`, "POST", req.body, ctx2);
|
|
6301
|
+
const result = await dispatcher.dispatch("POST", `/actions/${req.params.object}/${req.params.action}`, req.body, req.query, { request: req });
|
|
6165
6302
|
sendResult(result, res);
|
|
6166
6303
|
} catch (err) {
|
|
6167
6304
|
errorResponse(err, res);
|
|
@@ -6169,9 +6306,7 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6169
6306
|
});
|
|
6170
6307
|
server.post(`${base}/actions/:object/:action/:recordId`, async (req, res) => {
|
|
6171
6308
|
try {
|
|
6172
|
-
const
|
|
6173
|
-
if (req.params?.environmentId) ctx2.environmentId = req.params.environmentId;
|
|
6174
|
-
const result = await dispatcher.handleActions(`/${req.params.object}/${req.params.action}/${req.params.recordId}`, "POST", req.body, ctx2);
|
|
6309
|
+
const result = await dispatcher.dispatch("POST", `/actions/${req.params.object}/${req.params.action}/${req.params.recordId}`, req.body, req.query, { request: req });
|
|
6175
6310
|
sendResult(result, res);
|
|
6176
6311
|
} catch (err) {
|
|
6177
6312
|
errorResponse(err, res);
|