@gavdi/cap-mcp 0.9.0 → 0.9.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.
@@ -1,18 +1,36 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DEFAULT_ALL_RESOURCE_OPTIONS = exports.MCP_ANNOTATION_PROPS = exports.MCP_ANNOTATION_KEY = void 0;
4
+ /**
5
+ * MCP annotation constants and default configurations
6
+ * Defines the standard annotation keys and default values used throughout the plugin
7
+ */
8
+ /**
9
+ * Base key used to identify MCP annotations in CDS definitions
10
+ * All MCP annotations must start with this prefix
11
+ */
4
12
  exports.MCP_ANNOTATION_KEY = "@mcp";
13
+ /**
14
+ * Complete set of supported MCP annotation property names
15
+ * Maps logical names to their actual annotation keys used in CDS files
16
+ */
5
17
  exports.MCP_ANNOTATION_PROPS = {
6
- // Standard annotations - required for all
18
+ /** Name identifier annotation - required for all MCP elements */
7
19
  MCP_NAME: "@mcp.name",
20
+ /** Description annotation - required for all MCP elements */
8
21
  MCP_DESCRIPTION: "@mcp.description",
9
- // Resource annotations for MCP
22
+ /** Resource configuration annotation for CAP entities */
10
23
  MCP_RESOURCE: "@mcp.resource",
11
- // Tool annotations for MCP
24
+ /** Tool configuration annotation for CAP functions/actions */
12
25
  MCP_TOOL: "@mcp.tool",
13
- // Prompt annotations for MCP
26
+ /** Prompt templates annotation for CAP services */
14
27
  MCP_PROMPT: "@mcp.prompts",
15
28
  };
29
+ /**
30
+ * Default set of all available OData query options for MCP resources
31
+ * Used when @mcp.resource is set to `true` to enable all capabilities
32
+ * Includes: $filter, $orderby, $top, $skip, $select
33
+ */
16
34
  exports.DEFAULT_ALL_RESOURCE_OPTIONS = new Set([
17
35
  "filter",
18
36
  "orderby",
@@ -5,6 +5,12 @@ const logger_1 = require("../logger");
5
5
  const structures_1 = require("./structures");
6
6
  const utils_1 = require("./utils");
7
7
  const constants_1 = require("./constants");
8
+ /**
9
+ * Parses model definitions to extract MCP annotations and return them as a map of annotated entries
10
+ * @param model - The CSN model containing definitions to parse
11
+ * @returns A map of target names to their corresponding MCP annotation entries
12
+ * @throws Error if model lacks valid definitions
13
+ */
8
14
  function parseDefinitions(model) {
9
15
  if (!model.definitions) {
10
16
  logger_1.LOGGER.error("Invalid model loaded", model);
@@ -50,6 +56,11 @@ function parseDefinitions(model) {
50
56
  }
51
57
  return result;
52
58
  }
59
+ /**
60
+ * Parses MCP annotations from a definition object
61
+ * @param definition - The definition object to parse annotations from
62
+ * @returns Partial annotation structure or undefined if no MCP annotations found
63
+ */
53
64
  function parseAnnotations(definition) {
54
65
  if (!(0, utils_1.containsMcpAnnotation)(definition))
55
66
  return undefined;
@@ -82,6 +93,14 @@ function parseAnnotations(definition) {
82
93
  }
83
94
  return annotations;
84
95
  }
96
+ /**
97
+ * Constructs a resource annotation from parsed annotation data
98
+ * @param serviceName - Name of the service containing the resource
99
+ * @param target - Target entity name
100
+ * @param annotations - Parsed annotation structure
101
+ * @param definition - CSN definition object
102
+ * @returns Resource annotation or undefined if invalid
103
+ */
85
104
  function constructResourceAnnotation(serviceName, target, annotations, definition) {
86
105
  if (!(0, utils_1.isValidResourceAnnotation)(annotations))
87
106
  return undefined;
@@ -89,26 +108,45 @@ function constructResourceAnnotation(serviceName, target, annotations, definitio
89
108
  const { properties, resourceKeys } = (0, utils_1.parseResourceElements)(definition);
90
109
  return new structures_1.McpResourceAnnotation(annotations.name, annotations.description, target, serviceName, functionalities, properties, resourceKeys);
91
110
  }
111
+ /**
112
+ * Constructs a tool annotation from parsed annotation data
113
+ * @param serviceName - Name of the service containing the tool
114
+ * @param target - Target operation name
115
+ * @param annotations - Parsed annotation structure
116
+ * @param entityKey - Optional entity key for bound operations
117
+ * @param keyParams - Optional key parameters for bound operations
118
+ * @returns Tool annotation or undefined if invalid
119
+ */
92
120
  function constructToolAnnotation(serviceName, target, annotations, entityKey, keyParams) {
93
121
  if (!(0, utils_1.isValidToolAnnotation)(annotations))
94
122
  return undefined;
95
123
  const { parameters, operationKind } = (0, utils_1.parseOperationElements)(annotations);
96
124
  return new structures_1.McpToolAnnotation(annotations.name, annotations.description, target, serviceName, parameters, entityKey, operationKind, keyParams);
97
125
  }
126
+ /**
127
+ * Constructs a prompt annotation from parsed annotation data
128
+ * @param serviceName - Name of the service containing the prompts
129
+ * @param annotations - Parsed annotation structure
130
+ * @returns Prompt annotation or undefined if invalid
131
+ */
98
132
  function constructPromptAnnotation(serviceName, annotations) {
99
133
  if (!(0, utils_1.isValidPromptsAnnotation)(annotations))
100
134
  return undefined;
101
135
  return new structures_1.McpPromptAnnotation(annotations.name, annotations.description, serviceName, annotations.prompts);
102
136
  }
103
137
  /**
104
- * Parses the bound operations found on the entity definition if any.
105
- * This function mutates the passed result reference object.
138
+ * Parses bound operations (actions/functions) attached to an entity definition
139
+ * Extracts MCP tool annotations from entity-level operations and adds them to the result map
140
+ * @param serviceName - Name of the service containing the entity
141
+ * @param entityKey - Name of the entity that owns these bound operations
142
+ * @param definition - CSN entity definition containing bound operations
143
+ * @param resultRef - Map to store parsed annotations (mutated by this function)
106
144
  */
107
145
  function parseBoundOperations(serviceName, entityKey, definition, resultRef) {
108
146
  if (definition.kind !== "entity")
109
147
  return;
110
148
  const boundOperations = definition
111
- .actions; // Necessary due to missing type reference
149
+ .actions; // NOTE: Necessary due to missing type reference in cds-types
112
150
  if (!boundOperations)
113
151
  return;
114
152
  const keyParams = (0, utils_1.parseEntityKeys)(definition);
@@ -1,57 +1,136 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.McpPromptAnnotation = exports.McpToolAnnotation = exports.McpResourceAnnotation = exports.McpAnnotation = void 0;
4
+ /**
5
+ * Base class for all MCP annotations that provides common properties
6
+ * and functionality shared across different annotation types
7
+ */
4
8
  class McpAnnotation {
9
+ /** The name identifier for this annotation */
5
10
  _name;
11
+ /** AI agent readable description of what this annotation represents */
6
12
  _description;
13
+ /** The target entity, function, or service element this annotation applies to */
7
14
  _target;
15
+ /** The name of the CAP service this annotation belongs to */
8
16
  _serviceName;
17
+ /**
18
+ * Creates a new MCP annotation instance
19
+ * @param name - Unique identifier for this annotation
20
+ * @param description - Human-readable description
21
+ * @param target - The target element this annotation applies to
22
+ * @param serviceName - Name of the associated CAP service
23
+ */
9
24
  constructor(name, description, target, serviceName) {
10
25
  this._name = name;
11
26
  this._description = description;
12
27
  this._target = target;
13
28
  this._serviceName = serviceName;
14
29
  }
30
+ /**
31
+ * Gets the unique name identifier for this annotation
32
+ * @returns The annotation name
33
+ */
15
34
  get name() {
16
35
  return this._name;
17
36
  }
37
+ /**
38
+ * Gets the human-readable description of this annotation
39
+ * @returns The annotation description
40
+ */
18
41
  get description() {
19
42
  return this._description;
20
43
  }
44
+ /**
45
+ * Gets the target element this annotation applies to
46
+ * @returns The target identifier
47
+ */
21
48
  get target() {
22
49
  return this._target;
23
50
  }
51
+ /**
52
+ * Gets the name of the CAP service this annotation belongs to
53
+ * @returns The service name
54
+ */
24
55
  get serviceName() {
25
56
  return this._serviceName;
26
57
  }
27
58
  }
28
59
  exports.McpAnnotation = McpAnnotation;
60
+ /**
61
+ * Annotation class for MCP resources that can be queried with OData parameters
62
+ * Extends the base annotation with resource-specific configuration
63
+ */
29
64
  class McpResourceAnnotation extends McpAnnotation {
65
+ /** Set of OData query functionalities enabled for this resource */
30
66
  _functionalities;
67
+ /** Map of property names to their CDS types for validation */
31
68
  _properties;
69
+ /** Map of resource key fields to their types */
32
70
  _resourceKeys;
71
+ /**
72
+ * Creates a new MCP resource annotation
73
+ * @param name - Unique identifier for this resource
74
+ * @param description - Human-readable description
75
+ * @param target - The CAP entity this resource represents
76
+ * @param serviceName - Name of the associated CAP service
77
+ * @param functionalities - Set of enabled OData query options (filter, top, skip, etc.)
78
+ * @param properties - Map of entity properties to their CDS types
79
+ * @param resourceKeys - Map of key fields to their types
80
+ */
33
81
  constructor(name, description, target, serviceName, functionalities, properties, resourceKeys) {
34
82
  super(name, description, target, serviceName);
35
83
  this._functionalities = functionalities;
36
84
  this._properties = properties;
37
85
  this._resourceKeys = resourceKeys;
38
86
  }
87
+ /**
88
+ * Gets the set of enabled OData query functionalities
89
+ * @returns Set of available query options like 'filter', 'top', 'skip'
90
+ */
39
91
  get functionalities() {
40
92
  return this._functionalities;
41
93
  }
94
+ /**
95
+ * Gets the map of entity properties to their CDS types
96
+ * @returns Map of property names to type strings
97
+ */
42
98
  get properties() {
43
99
  return this._properties;
44
100
  }
101
+ /**
102
+ * Gets the map of resource key fields to their types
103
+ * @returns Map of key field names to type strings
104
+ */
45
105
  get resourceKeys() {
46
106
  return this._resourceKeys;
47
107
  }
48
108
  }
49
109
  exports.McpResourceAnnotation = McpResourceAnnotation;
110
+ /**
111
+ * Annotation class for MCP tools that represent executable CAP functions or actions
112
+ * Can be either bound (entity-level) or unbound (service-level) operations
113
+ */
50
114
  class McpToolAnnotation extends McpAnnotation {
115
+ /** Map of function parameters to their CDS types */
51
116
  _parameters;
117
+ /** Entity key field name for bound operations */
52
118
  _entityKey;
119
+ /** Type of operation: 'function' or 'action' */
53
120
  _operationKind;
121
+ /** Map of key field names to their types for bound operations */
54
122
  _keyTypeMap;
123
+ /**
124
+ * Creates a new MCP tool annotation
125
+ * @param name - Unique identifier for this tool
126
+ * @param description - Human-readable description
127
+ * @param operation - The CAP function or action name
128
+ * @param serviceName - Name of the associated CAP service
129
+ * @param parameters - Optional map of function parameters to their types
130
+ * @param entityKey - Optional entity key field for bound operations
131
+ * @param operationKind - Optional operation type ('function' or 'action')
132
+ * @param keyTypeMap - Optional map of key fields to types for bound operations
133
+ */
55
134
  constructor(name, description, operation, serviceName, parameters, entityKey, operationKind, keyTypeMap) {
56
135
  super(name, description, operation, serviceName);
57
136
  this._parameters = parameters;
@@ -59,26 +138,58 @@ class McpToolAnnotation extends McpAnnotation {
59
138
  this._operationKind = operationKind;
60
139
  this._keyTypeMap = keyTypeMap;
61
140
  }
141
+ /**
142
+ * Gets the map of function parameters to their CDS types
143
+ * @returns Map of parameter names to type strings, or undefined if no parameters
144
+ */
62
145
  get parameters() {
63
146
  return this._parameters;
64
147
  }
148
+ /**
149
+ * Gets the entity key field name for bound operations
150
+ * @returns Entity key field name, or undefined for unbound operations
151
+ */
65
152
  get entityKey() {
66
153
  return this._entityKey;
67
154
  }
155
+ /**
156
+ * Gets the operation kind (function or action)
157
+ * @returns Operation type string, or undefined if not specified
158
+ */
68
159
  get operationKind() {
69
160
  return this._operationKind;
70
161
  }
162
+ /**
163
+ * Gets the map of key field names to their types for bound operations
164
+ * @returns Map of key fields to types, or undefined for unbound operations
165
+ */
71
166
  get keyTypeMap() {
72
167
  return this._keyTypeMap;
73
168
  }
74
169
  }
75
170
  exports.McpToolAnnotation = McpToolAnnotation;
171
+ /**
172
+ * Annotation class for MCP prompts that define reusable prompt templates
173
+ * Applied at the service level to provide prompt templates with variable substitution
174
+ */
76
175
  class McpPromptAnnotation extends McpAnnotation {
176
+ /** Array of prompt template definitions */
77
177
  _prompts;
178
+ /**
179
+ * Creates a new MCP prompt annotation
180
+ * @param name - Unique identifier for this prompt collection
181
+ * @param description - Human-readable description
182
+ * @param serviceName - Name of the associated CAP service
183
+ * @param prompts - Array of prompt template definitions
184
+ */
78
185
  constructor(name, description, serviceName, prompts) {
79
186
  super(name, description, serviceName, serviceName);
80
187
  this._prompts = prompts;
81
188
  }
189
+ /**
190
+ * Gets the array of prompt template definitions
191
+ * @returns Array of prompt templates with their inputs and templates
192
+ */
82
193
  get prompts() {
83
194
  return this._prompts;
84
195
  }
@@ -12,6 +12,11 @@ exports.parseOperationElements = parseOperationElements;
12
12
  exports.parseEntityKeys = parseEntityKeys;
13
13
  const constants_1 = require("./constants");
14
14
  const logger_1 = require("../logger");
15
+ /**
16
+ * Splits a definition name into service name and target
17
+ * @param definition - The definition name to split
18
+ * @returns Object containing serviceName and target
19
+ */
15
20
  function splitDefinitionName(definition) {
16
21
  const splitted = definition.split(".");
17
22
  return {
@@ -19,6 +24,11 @@ function splitDefinitionName(definition) {
19
24
  target: splitted[1],
20
25
  };
21
26
  }
27
+ /**
28
+ * Checks if a definition contains any MCP annotations
29
+ * @param definition - The definition to check
30
+ * @returns True if MCP annotations are found, false otherwise
31
+ */
22
32
  function containsMcpAnnotation(definition) {
23
33
  for (const key of Object.keys(definition)) {
24
34
  if (!key.includes(constants_1.MCP_ANNOTATION_KEY))
@@ -27,6 +37,12 @@ function containsMcpAnnotation(definition) {
27
37
  }
28
38
  return false;
29
39
  }
40
+ /**
41
+ * Validates that required MCP annotations are present and valid
42
+ * @param annotations - The annotation structure to validate
43
+ * @returns True if valid, throws error if invalid
44
+ * @throws Error if required annotations are missing
45
+ */
30
46
  function containsRequiredAnnotations(annotations) {
31
47
  if (annotations.definition?.kind === "service")
32
48
  return true;
@@ -38,6 +54,12 @@ function containsRequiredAnnotations(annotations) {
38
54
  }
39
55
  return true;
40
56
  }
57
+ /**
58
+ * Validates a resource annotation structure
59
+ * @param annotations - The annotation structure to validate
60
+ * @returns True if valid, throws error if invalid
61
+ * @throws Error if resource annotation is invalid
62
+ */
41
63
  function isValidResourceAnnotation(annotations) {
42
64
  if (!annotations?.resource) {
43
65
  throw new Error(`Invalid annotation '${annotations.definition?.target}' - Missing required flag 'resource'`);
@@ -51,12 +73,24 @@ function isValidResourceAnnotation(annotations) {
51
73
  }
52
74
  return true;
53
75
  }
76
+ /**
77
+ * Validates a tool annotation structure
78
+ * @param annotations - The annotation structure to validate
79
+ * @returns True if valid, throws error if invalid
80
+ * @throws Error if tool annotation is invalid
81
+ */
54
82
  function isValidToolAnnotation(annotations) {
55
83
  if (!annotations?.tool) {
56
84
  throw new Error(`Invalid annotation '${annotations.definition?.target}' - Missing required flag 'tool'`);
57
85
  }
58
86
  return true;
59
87
  }
88
+ /**
89
+ * Validates a prompts annotation structure
90
+ * @param annotations - The annotation structure to validate
91
+ * @returns True if valid, throws error if invalid
92
+ * @throws Error if prompts annotation is invalid
93
+ */
60
94
  function isValidPromptsAnnotation(annotations) {
61
95
  if (!annotations?.prompts) {
62
96
  throw new Error(`Invalid annotation '${annotations.definition?.target}' - Missing prompts annotations`);
@@ -87,11 +121,21 @@ function isValidPromptsAnnotation(annotations) {
87
121
  }
88
122
  return true;
89
123
  }
124
+ /**
125
+ * Determines resource options from annotation structure
126
+ * @param annotations - The annotation structure to process
127
+ * @returns Set of resource options, defaults to all options if not specified
128
+ */
90
129
  function determineResourceOptions(annotations) {
91
130
  if (!Array.isArray(annotations.resource))
92
131
  return constants_1.DEFAULT_ALL_RESOURCE_OPTIONS;
93
132
  return new Set(annotations.resource);
94
133
  }
134
+ /**
135
+ * Parses resource elements from a definition to extract properties and keys
136
+ * @param definition - The definition to parse
137
+ * @returns Object containing properties and resource keys maps
138
+ */
95
139
  function parseResourceElements(definition) {
96
140
  const properties = new Map();
97
141
  const resourceKeys = new Map();
@@ -109,6 +153,11 @@ function parseResourceElements(definition) {
109
153
  resourceKeys,
110
154
  };
111
155
  }
156
+ /**
157
+ * Parses operation elements from annotation structure
158
+ * @param annotations - The annotation structure to parse
159
+ * @returns Object containing parameters and operation kind
160
+ */
112
161
  function parseOperationElements(annotations) {
113
162
  let parameters;
114
163
  const params = annotations.definition["params"];
@@ -123,6 +172,12 @@ function parseOperationElements(annotations) {
123
172
  operationKind: annotations.definition.kind,
124
173
  };
125
174
  }
175
+ /**
176
+ * Parses entity keys from a definition
177
+ * @param definition - The definition to parse keys from
178
+ * @returns Map of key names to their types
179
+ * @throws Error if invalid key type is found
180
+ */
126
181
  function parseEntityKeys(definition) {
127
182
  const result = new Map();
128
183
  for (const [k, v] of Object.entries(definition.elements)) {
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSafeEnvVar = getSafeEnvVar;
4
+ exports.isTestEnvironment = isTestEnvironment;
5
+ const logger_1 = require("../logger");
6
+ /**
7
+ * Dangerous characters that could be used for injection attacks
8
+ */
9
+ const DANGEROUS_CHARS = /[;&|`$()<>!]/g;
10
+ /**
11
+ * Validation patterns for specific environment variables
12
+ */
13
+ const VALIDATION_PATTERNS = {
14
+ npm_package_name: /^[a-zA-Z0-9\-_@/\.]+$/,
15
+ npm_package_version: /^\d+\.\d+\.\d+(-[a-zA-Z0-9\-\.]+)?(\+[a-zA-Z0-9\-\.]+)?$/,
16
+ NODE_ENV: /^(development|production|test)$/,
17
+ };
18
+ /**
19
+ * Sanitizes environment variable value by removing dangerous characters
20
+ * @param value - Raw environment variable value
21
+ * @param key - Environment variable key for logging
22
+ * @returns Sanitized value with dangerous characters removed
23
+ */
24
+ function sanitizeValue(value, key) {
25
+ const originalValue = value;
26
+ const sanitized = value.replace(DANGEROUS_CHARS, "").trim();
27
+ if (sanitized !== originalValue) {
28
+ logger_1.LOGGER.warn(`Environment variable '${key}' contained potentially dangerous characters and was sanitized`);
29
+ }
30
+ return sanitized;
31
+ }
32
+ /**
33
+ * Validates environment variable value against expected pattern
34
+ * @param value - Sanitized environment variable value
35
+ * @param key - Environment variable key
36
+ * @returns True if valid, false otherwise
37
+ */
38
+ function validateValue(value, key) {
39
+ const pattern = VALIDATION_PATTERNS[key];
40
+ if (!pattern)
41
+ return true; // No validation pattern defined
42
+ const isValid = pattern.test(value);
43
+ if (!isValid) {
44
+ logger_1.LOGGER.warn(`Environment variable '${key}' value '${value}' does not match expected pattern`);
45
+ }
46
+ return isValid;
47
+ }
48
+ /**
49
+ * Safely retrieves and sanitizes an environment variable
50
+ * @param key - Environment variable key
51
+ * @param defaultValue - Default value if environment variable is not set or invalid
52
+ * @returns Sanitized environment variable value or default
53
+ */
54
+ function getSafeEnvVar(key, defaultValue = "") {
55
+ const rawValue = process.env[key];
56
+ // Return default if not set or empty
57
+ if (!rawValue || rawValue.trim().length === 0) {
58
+ return defaultValue;
59
+ }
60
+ // Sanitize the value
61
+ const sanitized = sanitizeValue(rawValue, key);
62
+ // Validate if pattern exists
63
+ if (key in VALIDATION_PATTERNS) {
64
+ const isValid = validateValue(sanitized, key);
65
+ if (!isValid) {
66
+ logger_1.LOGGER.warn(`Using default value for invalid environment variable '${key}'`);
67
+ return defaultValue;
68
+ }
69
+ }
70
+ return sanitized;
71
+ }
72
+ /**
73
+ * Checks if current environment is test
74
+ * @returns True if NODE_ENV is 'test'
75
+ */
76
+ function isTestEnvironment() {
77
+ return getSafeEnvVar("NODE_ENV", "development") === "test";
78
+ }
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JsonParseError = exports.JsonParseErrorType = void 0;
4
+ exports.safeJsonParse = safeJsonParse;
5
+ exports.parseCAPConfiguration = parseCAPConfiguration;
6
+ exports.createSafeErrorMessage = createSafeErrorMessage;
7
+ const zod_1 = require("zod");
8
+ const logger_1 = require("../logger");
9
+ /**
10
+ * Configuration schema for validation
11
+ */
12
+ const CAPConfigurationSchema = zod_1.z.object({
13
+ name: zod_1.z.string(),
14
+ version: zod_1.z.string(),
15
+ capabilities: zod_1.z.object({
16
+ tools: zod_1.z.object({
17
+ listChanged: zod_1.z.boolean().optional(),
18
+ }),
19
+ resources: zod_1.z.object({
20
+ listChanged: zod_1.z.boolean().optional(),
21
+ subscribe: zod_1.z.boolean().optional(),
22
+ }),
23
+ prompts: zod_1.z.object({
24
+ listChanged: zod_1.z.boolean().optional(),
25
+ }),
26
+ }),
27
+ });
28
+ /**
29
+ * Error types for JSON parsing failures
30
+ */
31
+ var JsonParseErrorType;
32
+ (function (JsonParseErrorType) {
33
+ JsonParseErrorType["INVALID_INPUT"] = "INVALID_INPUT";
34
+ JsonParseErrorType["PARSE_ERROR"] = "PARSE_ERROR";
35
+ JsonParseErrorType["VALIDATION_ERROR"] = "VALIDATION_ERROR";
36
+ })(JsonParseErrorType || (exports.JsonParseErrorType = JsonParseErrorType = {}));
37
+ /**
38
+ * Custom error class for JSON parsing failures
39
+ */
40
+ class JsonParseError extends Error {
41
+ type;
42
+ details;
43
+ constructor(type, message, details) {
44
+ super(message);
45
+ this.type = type;
46
+ this.details = details;
47
+ this.name = "JsonParseError";
48
+ }
49
+ }
50
+ exports.JsonParseError = JsonParseError;
51
+ /**
52
+ * Validates basic JSON structure without parsing
53
+ * @param input - JSON string to validate
54
+ * @returns True if basic structure is valid
55
+ */
56
+ function hasValidJsonStructure(input) {
57
+ // Check for balanced braces and brackets
58
+ let braceCount = 0;
59
+ let bracketCount = 0;
60
+ let inString = false;
61
+ let escaped = false;
62
+ for (let i = 0; i < input.length; i++) {
63
+ const char = input[i];
64
+ if (escaped) {
65
+ escaped = false;
66
+ continue;
67
+ }
68
+ if (char === "\\") {
69
+ escaped = true;
70
+ continue;
71
+ }
72
+ if (char === '"') {
73
+ inString = !inString;
74
+ continue;
75
+ }
76
+ if (inString)
77
+ continue;
78
+ switch (char) {
79
+ case "{":
80
+ braceCount++;
81
+ break;
82
+ case "}":
83
+ braceCount--;
84
+ break;
85
+ case "[":
86
+ bracketCount++;
87
+ break;
88
+ case "]":
89
+ bracketCount--;
90
+ break;
91
+ }
92
+ // Early detection of imbalanced brackets
93
+ if (braceCount < 0 || bracketCount < 0) {
94
+ return false;
95
+ }
96
+ }
97
+ return braceCount === 0 && bracketCount === 0 && !inString;
98
+ }
99
+ /**
100
+ * Safely parses JSON with comprehensive security checks
101
+ * @param input - JSON string to parse
102
+ * @param schema - Zod schema for validation
103
+ * @returns Parsed and validated object or null if parsing fails
104
+ */
105
+ function safeJsonParse(input, schema) {
106
+ try {
107
+ // Input validation
108
+ if (typeof input !== "string") {
109
+ throw new JsonParseError(JsonParseErrorType.INVALID_INPUT, "Input must be a string", `Received: ${typeof input}`);
110
+ }
111
+ // Trim whitespace
112
+ const trimmed = input.trim();
113
+ if (trimmed.length === 0) {
114
+ throw new JsonParseError(JsonParseErrorType.INVALID_INPUT, "Input is empty or contains only whitespace");
115
+ }
116
+ // Basic structure validation
117
+ if (!hasValidJsonStructure(trimmed)) {
118
+ throw new JsonParseError(JsonParseErrorType.PARSE_ERROR, "Invalid JSON structure detected");
119
+ }
120
+ // Parse JSON
121
+ let parsed;
122
+ try {
123
+ parsed = JSON.parse(trimmed);
124
+ }
125
+ catch (error) {
126
+ throw new JsonParseError(JsonParseErrorType.PARSE_ERROR, "JSON parsing failed", error instanceof Error ? error.message : "Unknown parse error");
127
+ }
128
+ // Schema validation
129
+ const validationResult = schema.safeParse(parsed);
130
+ if (!validationResult.success) {
131
+ throw new JsonParseError(JsonParseErrorType.VALIDATION_ERROR, "JSON does not match expected schema", validationResult.error.message);
132
+ }
133
+ return validationResult.data;
134
+ }
135
+ catch (error) {
136
+ if (error instanceof JsonParseError) {
137
+ // Log detailed error for debugging (without exposing to user)
138
+ logger_1.LOGGER.warn(`Safe JSON parsing failed: ${error.type}`, {
139
+ message: error.message,
140
+ details: error.details,
141
+ });
142
+ }
143
+ else {
144
+ // Unexpected error
145
+ logger_1.LOGGER.warn("Unexpected error during JSON parsing", error);
146
+ }
147
+ return null;
148
+ }
149
+ }
150
+ /**
151
+ * Safely parses CAP configuration JSON
152
+ * @param input - JSON string containing CAP configuration
153
+ * @returns Parsed CAPConfiguration or null if parsing fails
154
+ */
155
+ function parseCAPConfiguration(input) {
156
+ return safeJsonParse(input, CAPConfigurationSchema);
157
+ }
158
+ /**
159
+ * Creates a generic error message for user-facing errors
160
+ * @param context - Context where the error occurred
161
+ * @returns Generic error message that doesn't expose sensitive information
162
+ */
163
+ function createSafeErrorMessage(context) {
164
+ return `Configuration parsing failed in ${context}. Please check the configuration format.`;
165
+ }