@gavdi/cap-mcp 0.9.9-alpha.3 → 0.10.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.
@@ -61,6 +61,45 @@ async function resolveServiceInstance(serviceName) {
61
61
  // NOTE: We use plain entity names (service projection) for queries.
62
62
  const MAX_TOP = 200;
63
63
  const TIMEOUT_MS = 10_000; // Standard timeout for tool calls (ms)
64
+ // Map OData operators to CDS/SQL operators for better performance and readability
65
+ const ODATA_TO_CDS_OPERATORS = new Map([
66
+ ["eq", "="],
67
+ ["ne", "!="],
68
+ ["gt", ">"],
69
+ ["ge", ">="],
70
+ ["lt", "<"],
71
+ ["le", "<="],
72
+ ]);
73
+ /**
74
+ * Builds enhanced query tool description with field types and association examples
75
+ */
76
+ function buildEnhancedQueryDescription(resAnno) {
77
+ const associations = Array.from(resAnno.properties.entries())
78
+ .filter(([, cdsType]) => String(cdsType).toLowerCase().includes("association"))
79
+ .map(([name]) => `${name}_ID`);
80
+ const baseDesc = `Query ${resAnno.target} with structured filters, select, orderby, top/skip.`;
81
+ const assocHint = associations.length > 0
82
+ ? ` IMPORTANT: For associations, always use foreign key fields (${associations.join(", ")}) - never use association names directly.`
83
+ : "";
84
+ return baseDesc + assocHint;
85
+ }
86
+ /**
87
+ * Builds field documentation for schema descriptions
88
+ */
89
+ function buildFieldDocumentation(resAnno) {
90
+ const docs = [];
91
+ for (const [propName, cdsType] of resAnno.properties.entries()) {
92
+ const isAssociation = String(cdsType).toLowerCase().includes("association");
93
+ if (isAssociation) {
94
+ docs.push(`${propName}(association: compare by key value)`);
95
+ docs.push(`${propName}_ID(foreign key for ${propName})`);
96
+ }
97
+ else {
98
+ docs.push(`${propName}(${String(cdsType).toLowerCase()})`);
99
+ }
100
+ }
101
+ return docs.join(", ");
102
+ }
64
103
  /**
65
104
  * Registers CRUD-like MCP tools for an annotated entity (resource).
66
105
  * Modes can be controlled globally via configuration and per-entity via @mcp.wrap.
@@ -115,9 +154,27 @@ function nameFor(service, entity, suffix) {
115
154
  function registerQueryTool(resAnno, server, authEnabled) {
116
155
  const toolName = nameFor(resAnno.serviceName, resAnno.target, "query");
117
156
  // Structured input schema for queries with guard for empty property lists
118
- const propKeys = Array.from(resAnno.properties.keys());
119
- const fieldEnum = (propKeys.length
120
- ? zod_1.z.enum(propKeys)
157
+ const allKeys = Array.from(resAnno.properties.keys());
158
+ const scalarKeys = Array.from(resAnno.properties.entries())
159
+ .filter(([, cdsType]) => !String(cdsType).toLowerCase().includes("association"))
160
+ .map(([name]) => name);
161
+ // Add foreign key fields for associations to scalar keys for select/orderby
162
+ for (const [propName, cdsType] of resAnno.properties.entries()) {
163
+ const isAssociation = String(cdsType).toLowerCase().includes("association");
164
+ if (isAssociation) {
165
+ scalarKeys.push(`${propName}_ID`);
166
+ }
167
+ }
168
+ // Build where field enum: use same fields as select (scalar + foreign keys)
169
+ // This ensures consistency - what you can select, you can filter by
170
+ const whereKeys = [...scalarKeys];
171
+ const whereFieldEnum = (whereKeys.length
172
+ ? zod_1.z.enum(whereKeys)
173
+ : zod_1.z
174
+ .enum(["__dummy__"])
175
+ .transform(() => "__dummy__"));
176
+ const selectFieldEnum = (scalarKeys.length
177
+ ? zod_1.z.enum(scalarKeys)
121
178
  : zod_1.z
122
179
  .enum(["__dummy__"])
123
180
  .transform(() => "__dummy__"));
@@ -131,16 +188,21 @@ function registerQueryTool(resAnno, server, authEnabled) {
131
188
  .default(25)
132
189
  .describe("Rows (default 25)"),
133
190
  skip: zod_1.z.number().int().min(0).default(0).describe("Offset"),
134
- select: zod_1.z.array(fieldEnum).optional(),
191
+ select: zod_1.z
192
+ .array(selectFieldEnum)
193
+ .optional()
194
+ .transform((val) => val && val.length > 0 ? val : undefined)
195
+ .describe(`Select/orderby allow only scalar fields: ${scalarKeys.join(", ")}`),
135
196
  orderby: zod_1.z
136
197
  .array(zod_1.z.object({
137
- field: fieldEnum,
198
+ field: selectFieldEnum,
138
199
  dir: zod_1.z.enum(["asc", "desc"]).default("asc"),
139
200
  }))
140
- .optional(),
201
+ .optional()
202
+ .transform((val) => val && val.length > 0 ? val : undefined),
141
203
  where: zod_1.z
142
204
  .array(zod_1.z.object({
143
- field: fieldEnum,
205
+ field: whereFieldEnum.describe(`FILTERABLE FIELDS: ${scalarKeys.join(", ")}. For associations use foreign key (author_ID), NOT association name (author).`),
144
206
  op: zod_1.z.enum([
145
207
  "eq",
146
208
  "ne",
@@ -160,15 +222,17 @@ function registerQueryTool(resAnno, server, authEnabled) {
160
222
  zod_1.z.array(zod_1.z.union([zod_1.z.string(), zod_1.z.number()])),
161
223
  ]),
162
224
  }))
163
- .optional(),
225
+ .optional()
226
+ .transform((val) => val && val.length > 0 ? val : undefined),
164
227
  q: zod_1.z.string().optional().describe("Quick text search"),
165
228
  return: zod_1.z.enum(["rows", "count", "aggregate"]).default("rows").optional(),
166
229
  aggregate: zod_1.z
167
230
  .array(zod_1.z.object({
168
- field: fieldEnum,
231
+ field: selectFieldEnum,
169
232
  fn: zod_1.z.enum(["sum", "avg", "min", "max", "count"]),
170
233
  }))
171
- .optional(),
234
+ .optional()
235
+ .transform((val) => (val && val.length > 0 ? val : undefined)),
172
236
  explain: zod_1.z.boolean().optional(),
173
237
  })
174
238
  .strict();
@@ -184,7 +248,8 @@ function registerQueryTool(resAnno, server, authEnabled) {
184
248
  explain: inputZod.shape.explain,
185
249
  };
186
250
  const hint = resAnno.wrap?.hint ? ` Hint: ${resAnno.wrap?.hint}` : "";
187
- const desc = `List ${resAnno.target}. Use structured filters (where), top/skip/orderby/select. For fields & examples call cap_describe_model.${hint}`;
251
+ const desc = `${buildEnhancedQueryDescription(resAnno)} CRITICAL: Use foreign key fields (e.g., author_ID) for associations - association names (e.g., author) won't work in filters.` +
252
+ hint;
188
253
  const queryHandler = async (rawArgs) => {
189
254
  const parsed = inputZod.safeParse(rawArgs);
190
255
  if (!parsed.success) {
@@ -203,7 +268,7 @@ function registerQueryTool(resAnno, server, authEnabled) {
203
268
  }
204
269
  let q;
205
270
  try {
206
- q = buildQuery(CDS, args, resAnno, propKeys);
271
+ q = buildQuery(CDS, args, resAnno, allKeys);
207
272
  }
208
273
  catch (e) {
209
274
  return (0, utils_2.toolError)("FILTER_PARSE_ERROR", e?.message || String(e));
@@ -564,8 +629,9 @@ function buildQuery(CDS, args, resAnno, propKeys) {
564
629
  let qy = SELECT.from(resAnno.target).limit(limitTop, limitSkip);
565
630
  if ((propKeys?.length ?? 0) === 0)
566
631
  return qy;
567
- if (args.select?.length)
632
+ if (args.select?.length) {
568
633
  qy = qy.columns(...args.select);
634
+ }
569
635
  if (args.orderby?.length) {
570
636
  // Map to CQN-compatible order by fragments
571
637
  const orderFragments = args.orderby.map((o) => `${o.field} ${o.dir}`);
@@ -575,29 +641,40 @@ function buildQuery(CDS, args, resAnno, propKeys) {
575
641
  const ands = [];
576
642
  if (args.q) {
577
643
  const textFields = Array.from(resAnno.properties.keys()).filter((k) => /string/i.test(String(resAnno.properties.get(k))));
578
- const ors = textFields.map((f) => CDS.parse.expr(`contains(${f}, '${String(args.q).replace(/'/g, "''")}')`));
579
- if (ors.length)
580
- ands.push(CDS.parse.expr(ors.map((x) => `(${x})`).join(" or ")));
644
+ const escaped = String(args.q).replace(/'/g, "''");
645
+ const ors = textFields.map((f) => `contains(${f}, '${escaped}')`);
646
+ if (ors.length) {
647
+ const orExpr = ors.map((x) => `(${x})`).join(" or ");
648
+ ands.push(CDS.parse.expr(orExpr));
649
+ }
581
650
  }
582
651
  for (const c of args.where || []) {
583
652
  const { field, op, value } = c;
653
+ // Field names are now consistent - use them directly
654
+ const actualField = field;
584
655
  if (op === "in" && Array.isArray(value)) {
585
656
  const list = value
586
657
  .map((v) => typeof v === "string" ? `'${v.replace(/'/g, "''")}'` : String(v))
587
658
  .join(",");
588
- ands.push(CDS.parse.expr(`${field} in (${list})`));
659
+ ands.push(CDS.parse.expr(`${actualField} in (${list})`));
589
660
  continue;
590
661
  }
591
662
  const lit = typeof value === "string"
592
663
  ? `'${String(value).replace(/'/g, "''")}'`
593
664
  : String(value);
665
+ // Map OData operators to CDS/SQL operators
666
+ const cdsOp = ODATA_TO_CDS_OPERATORS.get(op) ?? op;
594
667
  const expr = ["contains", "startswith", "endswith"].includes(op)
595
- ? `${op}(${field}, ${lit})`
596
- : `${field} ${op} ${lit}`;
668
+ ? `${op}(${actualField}, ${lit})`
669
+ : `${actualField} ${cdsOp} ${lit}`;
597
670
  ands.push(CDS.parse.expr(expr));
598
671
  }
599
- if (ands.length)
600
- qy = qy.where(ands);
672
+ if (ands.length) {
673
+ // Apply each condition individually - CDS will AND them together
674
+ for (const condition of ands) {
675
+ qy = qy.where(condition);
676
+ }
677
+ }
601
678
  }
602
679
  return qy;
603
680
  }
package/lib/mcp/tools.js CHANGED
@@ -6,6 +6,7 @@ const logger_1 = require("../logger");
6
6
  const constants_1 = require("./constants");
7
7
  const zod_1 = require("zod");
8
8
  const utils_2 = require("../auth/utils");
9
+ const elicited_input_1 = require("./elicited-input");
9
10
  /* @ts-ignore */
10
11
  const cds = global.cds || require("@sap/cds"); // This is a work around for missing cds context
11
12
  /**
@@ -37,7 +38,12 @@ function assignBoundOperation(params, model, server, authEnabled) {
37
38
  throw new Error("Bound operation cannot be assigned to tool list, missing keys");
38
39
  }
39
40
  const keys = buildToolParameters(model.keyTypeMap);
40
- const inputSchema = buildZodSchema({ ...keys, ...params });
41
+ const useElicitInput = (0, elicited_input_1.isElicitInput)(model.elicits);
42
+ const inputSchema = buildZodSchema({
43
+ ...keys,
44
+ ...(useElicitInput ? {} : params),
45
+ });
46
+ const elicitationRequests = (0, elicited_input_1.constructElicitationFunctions)(model, params);
41
47
  server.registerTool(model.name, {
42
48
  title: model.name,
43
49
  description: model.description,
@@ -69,11 +75,15 @@ function assignBoundOperation(params, model, server, authEnabled) {
69
75
  continue;
70
76
  operationInput[k] = v;
71
77
  }
78
+ const elicitationResult = await (0, elicited_input_1.handleElicitationRequests)(elicitationRequests, server);
79
+ if (elicitationResult?.earlyResponse) {
80
+ return elicitationResult.earlyResponse;
81
+ }
72
82
  const accessRights = (0, utils_2.getAccessRights)(authEnabled);
73
83
  const response = await service.tx({ user: accessRights }).send({
74
84
  event: model.target,
75
85
  entity: model.entityKey,
76
- data: operationInput,
86
+ data: elicitationResult?.data ?? operationInput,
77
87
  params: [operationKeys],
78
88
  });
79
89
  return (0, utils_1.asMcpResult)(response);
@@ -87,7 +97,9 @@ function assignBoundOperation(params, model, server, authEnabled) {
87
97
  * @param server - MCP server instance to register with
88
98
  */
89
99
  function assignUnboundOperation(params, model, server, authEnabled) {
90
- const inputSchema = buildZodSchema(params);
100
+ const useElicitInput = (0, elicited_input_1.isElicitInput)(model.elicits);
101
+ const inputSchema = buildZodSchema(useElicitInput ? {} : params);
102
+ const elicitationRequests = (0, elicited_input_1.constructElicitationFunctions)(model, params);
91
103
  server.registerTool(model.name, {
92
104
  title: model.name,
93
105
  description: model.description,
@@ -109,10 +121,14 @@ function assignUnboundOperation(params, model, server, authEnabled) {
109
121
  ],
110
122
  };
111
123
  }
124
+ const elicitationResult = await (0, elicited_input_1.handleElicitationRequests)(elicitationRequests, server);
125
+ if (elicitationResult?.earlyResponse) {
126
+ return elicitationResult.earlyResponse;
127
+ }
112
128
  const accessRights = (0, utils_2.getAccessRights)(authEnabled);
113
129
  const response = await service
114
130
  .tx({ user: accessRights })
115
- .send(model.target, args);
131
+ .send(model.target, elicitationResult?.data ?? args);
116
132
  return (0, utils_1.asMcpResult)(response);
117
133
  });
118
134
  }
@@ -130,27 +146,6 @@ function buildToolParameters(params) {
130
146
  }
131
147
  return result;
132
148
  }
133
- /**
134
- * Converts a value to a string representation suitable for MCP responses
135
- * Handles objects and arrays by JSON stringifying them instead of using String()
136
- * @param value - The value to convert to string
137
- * @returns String representation of the value
138
- */
139
- function formatResponseValue(value) {
140
- if (value === null || value === undefined) {
141
- return String(value);
142
- }
143
- if (typeof value === "object") {
144
- try {
145
- return JSON.stringify(value, null, 2);
146
- }
147
- catch (error) {
148
- // Fallback to String() if JSON.stringify fails (e.g., circular references)
149
- return String(value);
150
- }
151
- }
152
- return String(value);
153
- }
154
149
  /**
155
150
  * Constructs a complete Zod schema object for MCP tool input validation
156
151
  * @param params - Record of parameter names to Zod schema types
package/lib/mcp/utils.js CHANGED
@@ -18,6 +18,8 @@ function determineMcpParameterType(cdsType) {
18
18
  return zod_1.z.string();
19
19
  case "Integer":
20
20
  return zod_1.z.number();
21
+ case "Boolean":
22
+ return zod_1.z.boolean();
21
23
  default:
22
24
  return zod_1.z.string();
23
25
  }
package/lib/mcp.js CHANGED
@@ -40,9 +40,7 @@ class McpPlugin {
40
40
  logger_1.LOGGER.debug("Event received for 'bootstrap'");
41
41
  this.expressApp = app;
42
42
  this.expressApp.use("/mcp", express_1.default.json());
43
- // To make it more safe, if there is a mispelling we will always implement auth
44
- // Users will have to explicitly write none
45
- if (this.config.auth !== "none") {
43
+ if (this.config.auth === "inherit") {
46
44
  (0, utils_2.registerAuthMiddleware)(this.expressApp);
47
45
  }
48
46
  await this.registerApiEndpoints();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gavdi/cap-mcp",
3
- "version": "0.9.9-alpha.3",
3
+ "version": "0.10.0",
4
4
  "description": "MCP Pluging for CAP",
5
5
  "keywords": [
6
6
  "MCP",
@@ -41,7 +41,7 @@
41
41
  "express": "^4"
42
42
  },
43
43
  "dependencies": {
44
- "@modelcontextprotocol/sdk": "^1.17.4",
44
+ "@modelcontextprotocol/sdk": "^1.17.3",
45
45
  "zod": "^3.25.67",
46
46
  "zod-to-json-schema": "^3.24.5"
47
47
  },
package/lib/.DS_Store DELETED
Binary file
@@ -1,257 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.McpPromptAnnotation = exports.McpToolAnnotation = exports.McpResourceAnnotation = exports.McpAnnotation = exports.McpAnnotations = exports.McpAnnotationKey = void 0;
4
- exports.parseAnnotations = parseAnnotations;
5
- const utils_1 = require("./utils");
6
- const DEFAULT_ALL_RESOURCE_OPTIONS = new Set([
7
- "filter",
8
- "sort",
9
- "top",
10
- "skip",
11
- "select",
12
- ]);
13
- exports.McpAnnotationKey = "@mcp";
14
- exports.McpAnnotations = {
15
- // Resource annotations for MCP
16
- MCP_RESOURCE: "@mcp.resource",
17
- // Tool annotations for MCP
18
- MCP_TOOL_NAME: "@mcp.tool.name",
19
- MCP_TOOL_DESCRIPTION: "@mcp.tool.description",
20
- // Prompt annotations for MCP
21
- MCP_PROMPT: "@mcp.prompt",
22
- };
23
- class McpAnnotation {
24
- _target;
25
- _serviceName;
26
- constructor(target, serviceName) {
27
- this._target = target;
28
- this._serviceName = serviceName;
29
- }
30
- get target() {
31
- return this._target;
32
- }
33
- get serviceName() {
34
- return this._serviceName;
35
- }
36
- }
37
- exports.McpAnnotation = McpAnnotation;
38
- class McpResourceAnnotation extends McpAnnotation {
39
- _includeAll;
40
- _functionalities;
41
- _properties;
42
- constructor(target, serviceName, includeAll, functionalities, properties) {
43
- super(target, serviceName);
44
- this._includeAll = includeAll;
45
- this._functionalities = functionalities;
46
- this._properties = properties;
47
- }
48
- get includeAll() {
49
- return this._includeAll;
50
- }
51
- get functionalities() {
52
- return this._functionalities;
53
- }
54
- get properties() {
55
- return this._properties;
56
- }
57
- }
58
- exports.McpResourceAnnotation = McpResourceAnnotation;
59
- class McpToolAnnotation extends McpAnnotation {
60
- _name;
61
- _description;
62
- _parameters;
63
- _entityKey;
64
- _operationKind;
65
- _keyTypeMap;
66
- constructor(name, description, operation, serviceName, parameters, entityKey, operationKind, keyTypeMap) {
67
- super(operation, serviceName);
68
- this._name = name;
69
- this._description = description;
70
- this._parameters = parameters;
71
- this._entityKey = entityKey;
72
- this._operationKind = operationKind;
73
- this._keyTypeMap = keyTypeMap;
74
- }
75
- get name() {
76
- return this._name;
77
- }
78
- get description() {
79
- return this._description;
80
- }
81
- get parameters() {
82
- return this._parameters;
83
- }
84
- get entityKey() {
85
- return this._entityKey;
86
- }
87
- get operationKind() {
88
- return this._operationKind;
89
- }
90
- get keyTypeMap() {
91
- return this._keyTypeMap;
92
- }
93
- }
94
- exports.McpToolAnnotation = McpToolAnnotation;
95
- class McpPromptAnnotation extends McpAnnotation {
96
- _name;
97
- _template;
98
- constructor(target, serviceName, name, template) {
99
- super(target, serviceName);
100
- this._name = name;
101
- this._template = template;
102
- }
103
- get name() {
104
- return this._name;
105
- }
106
- get template() {
107
- return this._template;
108
- }
109
- }
110
- exports.McpPromptAnnotation = McpPromptAnnotation;
111
- function parseAnnotations(services) {
112
- const annotations = [];
113
- for (const serviceName of Object.keys(services)) {
114
- const srv = services[serviceName];
115
- if (srv.name === "CatalogService") {
116
- utils_1.LOGGER.debug("SERVICE: ", srv.model.definitions);
117
- }
118
- const entities = srv.entities;
119
- const operations = srv.operations; // Refers to action and function imports
120
- // Find entities
121
- for (const entityName of Object.keys(entities)) {
122
- const target = entities[entityName];
123
- const res = findEntityAnnotations(target, entityName, srv);
124
- if (target.actions) {
125
- const bound = parseBoundOperations(target.actions, entityName, target, srv);
126
- if (bound && bound.length > 0) {
127
- annotations.push(...bound);
128
- }
129
- }
130
- if (!res)
131
- continue;
132
- annotations.push(res);
133
- }
134
- // Find operations
135
- for (const operationName of Object.keys(operations)) {
136
- const op = operations[operationName];
137
- const res = findOperationAnnotations(op, operationName, srv);
138
- if (!res)
139
- continue;
140
- annotations.push(res);
141
- }
142
- }
143
- const result = formatAnnotations(annotations);
144
- return result;
145
- }
146
- function formatAnnotations(annotationList) {
147
- const result = new Map();
148
- for (const annotation of annotationList) {
149
- if (annotation.operation) {
150
- if (!annotation.annotations[exports.McpAnnotations.MCP_TOOL_NAME] ||
151
- !annotation.annotations[exports.McpAnnotations.MCP_TOOL_DESCRIPTION]) {
152
- utils_1.LOGGER.error(`Invalid annotation found for operation`, annotation);
153
- throw new Error(`Invalid annotations for operation '${annotation.operation}'`);
154
- }
155
- else if (typeof annotation.annotations[exports.McpAnnotations.MCP_TOOL_NAME] !==
156
- "string" ||
157
- typeof annotation.annotations[exports.McpAnnotations.MCP_TOOL_DESCRIPTION] !==
158
- "string") {
159
- utils_1.LOGGER.error("Invalid data for annotations", annotation);
160
- throw new Error(`Invalid annotation data for operation '${annotation.operation}'`);
161
- }
162
- const entry = new McpToolAnnotation(annotation.annotations[exports.McpAnnotations.MCP_TOOL_NAME], annotation.annotations[exports.McpAnnotations.MCP_TOOL_DESCRIPTION], annotation.operation, annotation.serviceName, mapOperationInput(annotation.context), // TODO: Parse the parameters from the context and place them in the class
163
- annotation.entityKey, annotation.operationKind, annotation.keyTypeMap);
164
- result.set(entry.target, entry);
165
- continue;
166
- }
167
- if (!annotation.entityKey) {
168
- utils_1.LOGGER.error("Invalid entry", annotation);
169
- throw new Error(`Invalid annotated entry found with no target`);
170
- }
171
- if (!annotation.annotations[exports.McpAnnotations.MCP_RESOURCE]) {
172
- utils_1.LOGGER.error("No valid annotations found for entry", annotation);
173
- throw new Error(`Invalid annotations for entry target: '${annotation.entityKey}'`);
174
- }
175
- const includeAll = annotation.annotations[exports.McpAnnotations.MCP_RESOURCE] === true;
176
- const functionalities = Array.isArray(annotation.annotations[exports.McpAnnotations.MCP_RESOURCE])
177
- ? new Set(annotation.annotations[exports.McpAnnotations.MCP_RESOURCE])
178
- : DEFAULT_ALL_RESOURCE_OPTIONS;
179
- const entry = new McpResourceAnnotation(annotation.entityKey, annotation.serviceName, includeAll, functionalities, (0, utils_1.parseEntityElements)(annotation.context));
180
- result.set(entry.target, entry);
181
- }
182
- utils_1.LOGGER.debug("Formatted annotations", result);
183
- return result;
184
- }
185
- function findEntityAnnotations(entry, entityKey, service) {
186
- const annotations = findAnnotations(entry);
187
- return Object.keys(annotations).length > 0
188
- ? {
189
- serviceName: service.name,
190
- annotations: annotations,
191
- entityKey: entityKey,
192
- context: entry,
193
- }
194
- : undefined;
195
- }
196
- function findOperationAnnotations(operation, operationName, service) {
197
- const annotations = findAnnotations(operation);
198
- return Object.keys(annotations).length > 0
199
- ? {
200
- serviceName: service.name,
201
- annotations: annotations,
202
- operation: operationName,
203
- operationKind: operation.kind,
204
- context: operation,
205
- }
206
- : undefined;
207
- }
208
- function parseBoundOperations(operations, entityKey, entity, service) {
209
- const res = new Array();
210
- for (const [operationName, operation] of Object.entries(operations)) {
211
- const annotation = findBoundOperationAnnotations(operation, operationName, entityKey, service);
212
- if (!annotation)
213
- continue;
214
- annotation.keyTypeMap = new Map();
215
- for (const [k, v] of Object.entries(entity.keys)) {
216
- if (!v.type) {
217
- utils_1.LOGGER.error("Invalid key type", k);
218
- throw new Error("Invalid key type found for bound operation");
219
- }
220
- annotation.keyTypeMap.set(k, v.type.replace("cds.", ""));
221
- }
222
- res.push(annotation);
223
- }
224
- return res;
225
- }
226
- function findBoundOperationAnnotations(operation, operationName, entityKey, service) {
227
- const annotations = findAnnotations(operation);
228
- return Object.keys(annotations).length > 0
229
- ? {
230
- serviceName: service.name,
231
- annotations: annotations,
232
- operation: operationName,
233
- operationKind: operation.kind,
234
- entityKey: entityKey,
235
- context: operation,
236
- }
237
- : undefined;
238
- }
239
- function findAnnotations(entry) {
240
- const annotations = {};
241
- for (const [k, v] of Object.entries(entry)) {
242
- if (!k.includes(exports.McpAnnotationKey))
243
- continue;
244
- annotations[k] = v;
245
- }
246
- return annotations;
247
- }
248
- function mapOperationInput(ctx) {
249
- const params = ctx["params"];
250
- if (!params)
251
- return undefined;
252
- const result = new Map();
253
- for (const [k, v] of Object.entries(params)) {
254
- result.set(k, v.type.replace("cds.", ""));
255
- }
256
- return result.size > 0 ? result : undefined;
257
- }
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
package/lib/auth/mock.js DELETED
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
package/lib/auth/types.js DELETED
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });