@gavdi/cap-mcp 1.2.0 → 1.2.1

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.
@@ -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?.[`${serviceName}.${target}`].elements ?? {})
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
@@ -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
  /**
@@ -322,7 +322,7 @@ function translateOperationRestriction(restrictionType) {
322
322
  case "CHANGE":
323
323
  return ["UPDATE"];
324
324
  case "WRITE":
325
- return ["CREATE", "READ", "UPDATE", "DELETE"];
325
+ return ["CREATE", "UPDATE", "DELETE"];
326
326
  case "*":
327
327
  return ["CREATE", "READ", "UPDATE", "DELETE"];
328
328
  default:
@@ -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.isConfigured()) {
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
+ }
@@ -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
- if (isAssociation) {
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gavdi/cap-mcp",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "MCP Pluging for CAP",
5
5
  "keywords": [
6
6
  "MCP",