@gavdi/cap-mcp 1.2.0 → 1.2.2
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.
|
@@ -39,19 +39,19 @@ function parseDefinitions(model) {
|
|
|
39
39
|
const resourceAnnotation = constructResourceAnnotation(serviceName, target, verifiedAnnotations, def, model);
|
|
40
40
|
if (!resourceAnnotation)
|
|
41
41
|
continue;
|
|
42
|
-
result.set(
|
|
42
|
+
result.set(`${serviceName}.${target}`, resourceAnnotation);
|
|
43
43
|
continue;
|
|
44
44
|
case "function":
|
|
45
45
|
const functionAnnotation = constructToolAnnotation(model, serviceName, target, verifiedAnnotations);
|
|
46
46
|
if (!functionAnnotation)
|
|
47
47
|
continue;
|
|
48
|
-
result.set(
|
|
48
|
+
result.set(`${serviceName}.${target}`, functionAnnotation);
|
|
49
49
|
continue;
|
|
50
50
|
case "action":
|
|
51
51
|
const actionAnnotation = constructToolAnnotation(model, serviceName, target, verifiedAnnotations);
|
|
52
52
|
if (!actionAnnotation)
|
|
53
53
|
continue;
|
|
54
|
-
result.set(
|
|
54
|
+
result.set(`${serviceName}.${target}`, actionAnnotation);
|
|
55
55
|
continue;
|
|
56
56
|
case "service":
|
|
57
57
|
const promptsAnnotation = constructPromptAnnotation(serviceName, verifiedAnnotations);
|
|
@@ -125,13 +125,17 @@ function parseAnnotations(definition) {
|
|
|
125
125
|
function constructResourceAnnotation(serviceName, target, annotations, definition, model) {
|
|
126
126
|
if (!(0, utils_1.isValidResourceAnnotation)(annotations))
|
|
127
127
|
return undefined;
|
|
128
|
+
const entityTarget = `${serviceName}.${target}`;
|
|
128
129
|
const functionalities = (0, utils_1.determineResourceOptions)(annotations);
|
|
129
|
-
const foreignKeys = new Map(Object.entries(model.definitions?.[
|
|
130
|
+
const foreignKeys = new Map(Object.entries(model.definitions?.[entityTarget].elements ?? {})
|
|
130
131
|
.filter(([_, v]) => v["@odata.foreignKey4"] !== undefined)
|
|
131
132
|
.map(([k, v]) => [k, v["@odata.foreignKey4"]]));
|
|
133
|
+
const computedFields = new Set(Object.entries(model.definitions?.[entityTarget].elements ?? {})
|
|
134
|
+
.filter(([_, v]) => new Map(Object.entries(v).map(([key, value]) => [key.toLowerCase(), value])).get("@core.computed"))
|
|
135
|
+
.map(([k, _]) => k));
|
|
132
136
|
const { properties, resourceKeys } = (0, utils_1.parseResourceElements)(definition, model);
|
|
133
137
|
const restrictions = (0, utils_1.parseCdsRestrictions)(annotations.restrict, annotations.requires);
|
|
134
|
-
return new structures_1.McpResourceAnnotation(annotations.name, annotations.description, target, serviceName, functionalities, properties, resourceKeys, foreignKeys, annotations.wrap, restrictions);
|
|
138
|
+
return new structures_1.McpResourceAnnotation(annotations.name, annotations.description, target, serviceName, functionalities, properties, resourceKeys, foreignKeys, annotations.wrap, restrictions, computedFields);
|
|
135
139
|
}
|
|
136
140
|
/**
|
|
137
141
|
* Constructs a tool annotation from parsed annotation data
|
|
@@ -193,6 +197,6 @@ function parseBoundOperations(model, serviceName, entityKey, definition, resultR
|
|
|
193
197
|
const toolAnnotation = constructToolAnnotation(model, serviceName, k, verifiedAnnotations, entityKey, keyParams);
|
|
194
198
|
if (!toolAnnotation)
|
|
195
199
|
continue;
|
|
196
|
-
resultRef.set(k
|
|
200
|
+
resultRef.set(`${serviceName}.${entityKey}.${k}`, toolAnnotation);
|
|
197
201
|
}
|
|
198
202
|
}
|
|
@@ -84,6 +84,8 @@ class McpResourceAnnotation extends McpAnnotation {
|
|
|
84
84
|
_wrap;
|
|
85
85
|
/** Map of foreign keys property -> associated entity */
|
|
86
86
|
_foreignKeys;
|
|
87
|
+
/** Set of computed field names */
|
|
88
|
+
_computedFields;
|
|
87
89
|
/**
|
|
88
90
|
* Creates a new MCP resource annotation
|
|
89
91
|
* @param name - Unique identifier for this resource
|
|
@@ -96,14 +98,16 @@ class McpResourceAnnotation extends McpAnnotation {
|
|
|
96
98
|
* @param foreignKeys - Map of foreign keys used by entity
|
|
97
99
|
* @param wrap - Wrap usage
|
|
98
100
|
* @param restrictions - Optional restrictions based on CDS roles
|
|
101
|
+
* @param computedFields - Optional set of fields that are computed and should be ignored in create scenarios
|
|
99
102
|
*/
|
|
100
|
-
constructor(name, description, target, serviceName, functionalities, properties, resourceKeys, foreignKeys, wrap, restrictions) {
|
|
103
|
+
constructor(name, description, target, serviceName, functionalities, properties, resourceKeys, foreignKeys, wrap, restrictions, computedFields) {
|
|
101
104
|
super(name, description, target, serviceName, restrictions ?? []);
|
|
102
105
|
this._functionalities = functionalities;
|
|
103
106
|
this._properties = properties;
|
|
104
107
|
this._resourceKeys = resourceKeys;
|
|
105
108
|
this._wrap = wrap;
|
|
106
109
|
this._foreignKeys = foreignKeys;
|
|
110
|
+
this._computedFields = computedFields;
|
|
107
111
|
}
|
|
108
112
|
/**
|
|
109
113
|
* Gets the set of enabled OData query functionalities
|
|
@@ -139,6 +143,12 @@ class McpResourceAnnotation extends McpAnnotation {
|
|
|
139
143
|
get wrap() {
|
|
140
144
|
return this._wrap;
|
|
141
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Gets the computed fields if any are available
|
|
148
|
+
*/
|
|
149
|
+
get computedFields() {
|
|
150
|
+
return this._computedFields;
|
|
151
|
+
}
|
|
142
152
|
}
|
|
143
153
|
exports.McpResourceAnnotation = McpResourceAnnotation;
|
|
144
154
|
/**
|
package/lib/annotations/utils.js
CHANGED
|
@@ -322,7 +322,7 @@ function translateOperationRestriction(restrictionType) {
|
|
|
322
322
|
case "CHANGE":
|
|
323
323
|
return ["UPDATE"];
|
|
324
324
|
case "WRITE":
|
|
325
|
-
return ["CREATE", "
|
|
325
|
+
return ["CREATE", "UPDATE", "DELETE"];
|
|
326
326
|
case "*":
|
|
327
327
|
return ["CREATE", "READ", "UPDATE", "DELETE"];
|
|
328
328
|
default:
|
package/lib/auth/factory.js
CHANGED
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.authHandlerFactory = authHandlerFactory;
|
|
4
4
|
exports.errorHandlerFactory = errorHandlerFactory;
|
|
5
5
|
const xsuaa_service_1 = require("./xsuaa-service");
|
|
6
|
+
const utils_1 = require("./utils");
|
|
7
|
+
const logger_1 = require("../logger");
|
|
6
8
|
/** JSON-RPC 2.0 error code for unauthorized requests */
|
|
7
9
|
const RPC_UNAUTHORIZED = 10;
|
|
8
10
|
/* @ts-ignore */
|
|
@@ -33,7 +35,8 @@ const cds = global.cds || require("@sap/cds"); // This is a work around for miss
|
|
|
33
35
|
*/
|
|
34
36
|
function authHandlerFactory() {
|
|
35
37
|
const authKind = cds.env.requires.auth.kind;
|
|
36
|
-
const xsuaaService = new xsuaa_service_1.XSUAAService();
|
|
38
|
+
const xsuaaService = !(0, utils_1.useMockAuth)(authKind) ? new xsuaa_service_1.XSUAAService() : undefined;
|
|
39
|
+
logger_1.LOGGER.debug("Authentication kind", authKind);
|
|
37
40
|
return async (req, res, next) => {
|
|
38
41
|
if (!req.headers.authorization && authKind !== "dummy") {
|
|
39
42
|
res.status(401).json({
|
|
@@ -48,7 +51,7 @@ function authHandlerFactory() {
|
|
|
48
51
|
}
|
|
49
52
|
// For XSUAA/JWT auth types, use @sap/xssec for validation
|
|
50
53
|
if ((authKind === "jwt" || authKind === "xsuaa" || authKind === "ias") &&
|
|
51
|
-
xsuaaService
|
|
54
|
+
xsuaaService?.isConfigured()) {
|
|
52
55
|
const securityContext = await xsuaaService.createSecurityContext(req);
|
|
53
56
|
if (!securityContext) {
|
|
54
57
|
res.status(401).json({
|
package/lib/auth/utils.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.getAccessRights = getAccessRights;
|
|
|
8
8
|
exports.registerAuthMiddleware = registerAuthMiddleware;
|
|
9
9
|
exports.hasToolOperationAccess = hasToolOperationAccess;
|
|
10
10
|
exports.getWrapAccesses = getWrapAccesses;
|
|
11
|
+
exports.useMockAuth = useMockAuth;
|
|
11
12
|
const express_1 = __importDefault(require("express"));
|
|
12
13
|
const helmet_1 = __importDefault(require("helmet"));
|
|
13
14
|
const factory_1 = require("./factory");
|
|
@@ -480,3 +481,10 @@ function getWrapAccesses(user, restrictions) {
|
|
|
480
481
|
}
|
|
481
482
|
return access;
|
|
482
483
|
}
|
|
484
|
+
/**
|
|
485
|
+
* Utility method for checking whether auth used is mocked and not live
|
|
486
|
+
* @returns boolean
|
|
487
|
+
*/
|
|
488
|
+
function useMockAuth(authKind) {
|
|
489
|
+
return authKind !== "jwt" && authKind !== "ias" && authKind !== "xsuaa";
|
|
490
|
+
}
|
package/lib/mcp/entity-tools.js
CHANGED
|
@@ -344,7 +344,8 @@ function registerCreateTool(resAnno, server, authEnabled) {
|
|
|
344
344
|
const inputSchema = {};
|
|
345
345
|
for (const [propName, cdsType] of resAnno.properties.entries()) {
|
|
346
346
|
const isAssociation = String(cdsType).toLowerCase().includes("association");
|
|
347
|
-
|
|
347
|
+
const isComputed = resAnno.computedFields?.has(propName);
|
|
348
|
+
if (isAssociation || isComputed) {
|
|
348
349
|
// Association keys are supplied directly from model loading as of v1.1.2
|
|
349
350
|
continue;
|
|
350
351
|
}
|
|
@@ -431,8 +432,9 @@ function registerUpdateTool(resAnno, server, authEnabled) {
|
|
|
431
432
|
for (const [propName, cdsType] of resAnno.properties.entries()) {
|
|
432
433
|
if (resAnno.resourceKeys.has(propName))
|
|
433
434
|
continue;
|
|
435
|
+
const isComputed = resAnno.computedFields?.has(propName);
|
|
434
436
|
const isAssociation = String(cdsType).toLowerCase().includes("association");
|
|
435
|
-
if (isAssociation) {
|
|
437
|
+
if (isAssociation || isComputed) {
|
|
436
438
|
// Association keys are supplied directly from model loading as of v1.1.2
|
|
437
439
|
continue;
|
|
438
440
|
}
|