@gavdi/cap-mcp 0.9.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/LICENSE.md +191 -0
- package/README.md +276 -0
- package/cds-plugin.js +17 -0
- package/index.cds +1 -0
- package/lib/annotations/constants.js +22 -0
- package/lib/annotations/parser.js +128 -0
- package/lib/annotations/structures.js +86 -0
- package/lib/annotations/types.js +2 -0
- package/lib/annotations/utils.js +138 -0
- package/lib/annotations.js +257 -0
- package/lib/auth/types.js +2 -0
- package/lib/config/loader.js +58 -0
- package/lib/config/types.js +27 -0
- package/lib/logger.js +6 -0
- package/lib/mcp/constants.js +6 -0
- package/lib/mcp/factory.js +41 -0
- package/lib/mcp/prompts.js +57 -0
- package/lib/mcp/resources.js +94 -0
- package/lib/mcp/tools.js +106 -0
- package/lib/mcp/types.js +2 -0
- package/lib/mcp/utils.js +62 -0
- package/lib/mcp.js +104 -0
- package/lib/types.js +2 -0
- package/lib/utils.js +136 -0
- package/package.json +67 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.McpPromptAnnotation = exports.McpToolAnnotation = exports.McpResourceAnnotation = exports.McpAnnotation = void 0;
|
4
|
+
class McpAnnotation {
|
5
|
+
_name;
|
6
|
+
_description;
|
7
|
+
_target;
|
8
|
+
_serviceName;
|
9
|
+
constructor(name, description, target, serviceName) {
|
10
|
+
this._name = name;
|
11
|
+
this._description = description;
|
12
|
+
this._target = target;
|
13
|
+
this._serviceName = serviceName;
|
14
|
+
}
|
15
|
+
get name() {
|
16
|
+
return this._name;
|
17
|
+
}
|
18
|
+
get description() {
|
19
|
+
return this._description;
|
20
|
+
}
|
21
|
+
get target() {
|
22
|
+
return this._target;
|
23
|
+
}
|
24
|
+
get serviceName() {
|
25
|
+
return this._serviceName;
|
26
|
+
}
|
27
|
+
}
|
28
|
+
exports.McpAnnotation = McpAnnotation;
|
29
|
+
class McpResourceAnnotation extends McpAnnotation {
|
30
|
+
_functionalities;
|
31
|
+
_properties;
|
32
|
+
_resourceKeys;
|
33
|
+
constructor(name, description, target, serviceName, functionalities, properties, resourceKeys) {
|
34
|
+
super(name, description, target, serviceName);
|
35
|
+
this._functionalities = functionalities;
|
36
|
+
this._properties = properties;
|
37
|
+
this._resourceKeys = resourceKeys;
|
38
|
+
}
|
39
|
+
get functionalities() {
|
40
|
+
return this._functionalities;
|
41
|
+
}
|
42
|
+
get properties() {
|
43
|
+
return this._properties;
|
44
|
+
}
|
45
|
+
get resourceKeys() {
|
46
|
+
return this._resourceKeys;
|
47
|
+
}
|
48
|
+
}
|
49
|
+
exports.McpResourceAnnotation = McpResourceAnnotation;
|
50
|
+
class McpToolAnnotation extends McpAnnotation {
|
51
|
+
_parameters;
|
52
|
+
_entityKey;
|
53
|
+
_operationKind;
|
54
|
+
_keyTypeMap;
|
55
|
+
constructor(name, description, operation, serviceName, parameters, entityKey, operationKind, keyTypeMap) {
|
56
|
+
super(name, description, operation, serviceName);
|
57
|
+
this._parameters = parameters;
|
58
|
+
this._entityKey = entityKey;
|
59
|
+
this._operationKind = operationKind;
|
60
|
+
this._keyTypeMap = keyTypeMap;
|
61
|
+
}
|
62
|
+
get parameters() {
|
63
|
+
return this._parameters;
|
64
|
+
}
|
65
|
+
get entityKey() {
|
66
|
+
return this._entityKey;
|
67
|
+
}
|
68
|
+
get operationKind() {
|
69
|
+
return this._operationKind;
|
70
|
+
}
|
71
|
+
get keyTypeMap() {
|
72
|
+
return this._keyTypeMap;
|
73
|
+
}
|
74
|
+
}
|
75
|
+
exports.McpToolAnnotation = McpToolAnnotation;
|
76
|
+
class McpPromptAnnotation extends McpAnnotation {
|
77
|
+
_prompts;
|
78
|
+
constructor(name, description, serviceName, prompts) {
|
79
|
+
super(name, description, serviceName, serviceName);
|
80
|
+
this._prompts = prompts;
|
81
|
+
}
|
82
|
+
get prompts() {
|
83
|
+
return this._prompts;
|
84
|
+
}
|
85
|
+
}
|
86
|
+
exports.McpPromptAnnotation = McpPromptAnnotation;
|
@@ -0,0 +1,138 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.splitDefinitionName = splitDefinitionName;
|
4
|
+
exports.containsMcpAnnotation = containsMcpAnnotation;
|
5
|
+
exports.containsRequiredAnnotations = containsRequiredAnnotations;
|
6
|
+
exports.isValidResourceAnnotation = isValidResourceAnnotation;
|
7
|
+
exports.isValidToolAnnotation = isValidToolAnnotation;
|
8
|
+
exports.isValidPromptsAnnotation = isValidPromptsAnnotation;
|
9
|
+
exports.determineResourceOptions = determineResourceOptions;
|
10
|
+
exports.parseResourceElements = parseResourceElements;
|
11
|
+
exports.parseOperationElements = parseOperationElements;
|
12
|
+
exports.parseEntityKeys = parseEntityKeys;
|
13
|
+
const constants_1 = require("./constants");
|
14
|
+
const logger_1 = require("../logger");
|
15
|
+
function splitDefinitionName(definition) {
|
16
|
+
const splitted = definition.split(".");
|
17
|
+
return {
|
18
|
+
serviceName: splitted[0],
|
19
|
+
target: splitted[1],
|
20
|
+
};
|
21
|
+
}
|
22
|
+
function containsMcpAnnotation(definition) {
|
23
|
+
for (const key of Object.keys(definition)) {
|
24
|
+
if (!key.includes(constants_1.MCP_ANNOTATION_KEY))
|
25
|
+
continue;
|
26
|
+
return true;
|
27
|
+
}
|
28
|
+
return false;
|
29
|
+
}
|
30
|
+
function containsRequiredAnnotations(annotations) {
|
31
|
+
if (annotations.definition?.kind === "service")
|
32
|
+
return true;
|
33
|
+
if (!annotations?.name || annotations.name.length <= 0) {
|
34
|
+
throw new Error(`Invalid annotation '${annotations.definition?.target}' - Missing required property 'name'`);
|
35
|
+
}
|
36
|
+
if (!annotations?.description || annotations.description.length <= 0) {
|
37
|
+
throw new Error("Invalid annotation - Missing required property 'description'");
|
38
|
+
}
|
39
|
+
return true;
|
40
|
+
}
|
41
|
+
function isValidResourceAnnotation(annotations) {
|
42
|
+
if (!annotations?.resource) {
|
43
|
+
throw new Error(`Invalid annotation '${annotations.definition?.target}' - Missing required flag 'resource'`);
|
44
|
+
}
|
45
|
+
if (Array.isArray(annotations.resource)) {
|
46
|
+
for (const el of annotations.resource) {
|
47
|
+
if (constants_1.DEFAULT_ALL_RESOURCE_OPTIONS.has(el))
|
48
|
+
continue;
|
49
|
+
throw new Error(`Invalid annotation '${annotations.definition?.target}' - Invalid resource option: ${el}`);
|
50
|
+
}
|
51
|
+
}
|
52
|
+
return true;
|
53
|
+
}
|
54
|
+
function isValidToolAnnotation(annotations) {
|
55
|
+
if (!annotations?.tool) {
|
56
|
+
throw new Error(`Invalid annotation '${annotations.definition?.target}' - Missing required flag 'tool'`);
|
57
|
+
}
|
58
|
+
return true;
|
59
|
+
}
|
60
|
+
function isValidPromptsAnnotation(annotations) {
|
61
|
+
if (!annotations?.prompts) {
|
62
|
+
throw new Error(`Invalid annotation '${annotations.definition?.target}' - Missing prompts annotations`);
|
63
|
+
}
|
64
|
+
for (const prompt of annotations.prompts) {
|
65
|
+
if (!prompt.template || prompt.template.length <= 0) {
|
66
|
+
throw new Error(`Invalid annotation '${annotations.definition?.target}' - Missing valid template`);
|
67
|
+
}
|
68
|
+
if (!prompt.name || prompt.name.length <= 0) {
|
69
|
+
throw new Error(`Invalid annotation '${annotations.definition?.target}' - Missing valid name`);
|
70
|
+
}
|
71
|
+
if (!prompt.title || prompt.title.length <= 0) {
|
72
|
+
throw new Error(`Invalid annotation '${annotations.definition?.target}' - Missing valid title`);
|
73
|
+
}
|
74
|
+
if (!prompt.role ||
|
75
|
+
(prompt.role !== "user" && prompt.role !== "assistant")) {
|
76
|
+
throw new Error(`Invalid annotation '${annotations.definition?.target}' - Role must be 'user' or 'assistant'`);
|
77
|
+
}
|
78
|
+
prompt.inputs?.forEach((el) => {
|
79
|
+
if (!el.key || el.key.length <= 0) {
|
80
|
+
throw new Error(`Invalid annotation '${annotations.definition?.target}' - missing input key`);
|
81
|
+
}
|
82
|
+
if (!el.type || el.type.length <= 0) {
|
83
|
+
throw new Error(`Invalid annotation '${annotations.definition?.target}' - missing input type`);
|
84
|
+
}
|
85
|
+
// TODO: Verify the input type against valid data types
|
86
|
+
});
|
87
|
+
}
|
88
|
+
return true;
|
89
|
+
}
|
90
|
+
function determineResourceOptions(annotations) {
|
91
|
+
if (!Array.isArray(annotations.resource))
|
92
|
+
return constants_1.DEFAULT_ALL_RESOURCE_OPTIONS;
|
93
|
+
return new Set(annotations.resource);
|
94
|
+
}
|
95
|
+
function parseResourceElements(definition) {
|
96
|
+
const properties = new Map();
|
97
|
+
const resourceKeys = new Map();
|
98
|
+
for (const [key, value] of Object.entries(definition.elements)) {
|
99
|
+
if (!value.type)
|
100
|
+
continue;
|
101
|
+
const parsedType = value.type.replace("cds.", "");
|
102
|
+
properties.set(key, parsedType);
|
103
|
+
if (!value.key)
|
104
|
+
continue;
|
105
|
+
resourceKeys.set(key, parsedType);
|
106
|
+
}
|
107
|
+
return {
|
108
|
+
properties,
|
109
|
+
resourceKeys,
|
110
|
+
};
|
111
|
+
}
|
112
|
+
function parseOperationElements(annotations) {
|
113
|
+
let parameters;
|
114
|
+
const params = annotations.definition["params"];
|
115
|
+
if (params && Object.entries(params).length > 0) {
|
116
|
+
parameters = new Map();
|
117
|
+
for (const [k, v] of Object.entries(params)) {
|
118
|
+
parameters.set(k, v.type.replace("cds.", ""));
|
119
|
+
}
|
120
|
+
}
|
121
|
+
return {
|
122
|
+
parameters,
|
123
|
+
operationKind: annotations.definition.kind,
|
124
|
+
};
|
125
|
+
}
|
126
|
+
function parseEntityKeys(definition) {
|
127
|
+
const result = new Map();
|
128
|
+
for (const [k, v] of Object.entries(definition.elements)) {
|
129
|
+
if (!v.key)
|
130
|
+
continue;
|
131
|
+
if (!v.type) {
|
132
|
+
logger_1.LOGGER.error("Invalid key type", k);
|
133
|
+
throw new Error("Invalid key type found for bound operation");
|
134
|
+
}
|
135
|
+
result.set(k, v.type.replace("cds.", ""));
|
136
|
+
}
|
137
|
+
return result;
|
138
|
+
}
|
@@ -0,0 +1,257 @@
|
|
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
|
+
}
|
@@ -0,0 +1,58 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.loadConfiguration = loadConfiguration;
|
4
|
+
const logger_1 = require("../logger");
|
5
|
+
/* @ts-ignore */
|
6
|
+
const cds = global.cds || require("@sap/cds"); // This is a work around for missing cds context
|
7
|
+
const ENV_NPM_PACKAGE_NAME = "npm_package_name";
|
8
|
+
const ENV_NPM_PACKAGE_VERSION = "npm_package_version";
|
9
|
+
const DEFAULT_PROJECT_INFO = {
|
10
|
+
name: "cap-mcp-server",
|
11
|
+
version: "1.0.0",
|
12
|
+
};
|
13
|
+
function loadConfiguration() {
|
14
|
+
const packageInfo = getProjectInfo();
|
15
|
+
const cdsEnv = loadCdsEnvConfiguration();
|
16
|
+
return {
|
17
|
+
name: cdsEnv?.name ?? packageInfo.name,
|
18
|
+
version: cdsEnv?.version ?? packageInfo.version,
|
19
|
+
capabilities: {
|
20
|
+
tools: cdsEnv?.capabilities?.tools ?? { listChanged: true },
|
21
|
+
resources: cdsEnv?.capabilities?.resources ?? { listChanged: true },
|
22
|
+
prompts: cdsEnv?.capabilities?.prompts ?? { listChanged: true },
|
23
|
+
},
|
24
|
+
};
|
25
|
+
}
|
26
|
+
/**
|
27
|
+
* Retrieves the current runtime's project information.
|
28
|
+
* This is used to distinguish the MCP server, by associating it with its parent application.
|
29
|
+
*
|
30
|
+
* In case of an error, the project info will default to plugin defaults.
|
31
|
+
* See constants for reference.
|
32
|
+
*/
|
33
|
+
function getProjectInfo() {
|
34
|
+
try {
|
35
|
+
return {
|
36
|
+
name: process.env[ENV_NPM_PACKAGE_NAME] ?? DEFAULT_PROJECT_INFO.name,
|
37
|
+
version: process.env[ENV_NPM_PACKAGE_VERSION] ?? DEFAULT_PROJECT_INFO.version,
|
38
|
+
};
|
39
|
+
}
|
40
|
+
catch (e) {
|
41
|
+
logger_1.LOGGER.warn("Failed to dynamically load project info, reverting to defaults. Error: ", e);
|
42
|
+
return DEFAULT_PROJECT_INFO;
|
43
|
+
}
|
44
|
+
}
|
45
|
+
function loadCdsEnvConfiguration() {
|
46
|
+
const config = cds.env.mcp;
|
47
|
+
if (!config)
|
48
|
+
return undefined;
|
49
|
+
else if (typeof config === "object")
|
50
|
+
return config;
|
51
|
+
try {
|
52
|
+
return JSON.parse(config);
|
53
|
+
}
|
54
|
+
catch (_) {
|
55
|
+
logger_1.LOGGER.warn("Could not parse the configuration object from cdsrc");
|
56
|
+
return undefined;
|
57
|
+
}
|
58
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
"use strict";
|
2
|
+
/* ======================================
|
3
|
+
* How it will look in the .cdsrc or package.json
|
4
|
+
* "cds": {
|
5
|
+
* ...
|
6
|
+
* "mcp": {
|
7
|
+
* "name": "my-mcp-server", // This is optional - otherwise grabbed from package.json name
|
8
|
+
* "version": "1.0.0", // Optional, otherwise grabbed from package.json version
|
9
|
+
* "auth": "inherit", // By default this will inherit auth from CAP, otherwise values can be 'inherit'|'api-key'|'none'
|
10
|
+
* "capabilities": {
|
11
|
+
* "resources": {
|
12
|
+
* "listChanged": true, // If not provided - default value = true
|
13
|
+
* "subscribe": true, // If not provided - default value = false
|
14
|
+
* },
|
15
|
+
* "tools": {
|
16
|
+
* "listChanged": true // If not provided - default value = true
|
17
|
+
* },
|
18
|
+
* "prompts": {
|
19
|
+
* "listChanged": true // If not provided - default value = true
|
20
|
+
* }
|
21
|
+
* }
|
22
|
+
* },
|
23
|
+
* ...
|
24
|
+
* }
|
25
|
+
* ======================================
|
26
|
+
*/
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/lib/logger.js
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.NEW_LINE = exports.MCP_SESSION_HEADER = exports.ERR_MISSING_SERVICE = void 0;
|
4
|
+
exports.ERR_MISSING_SERVICE = "Error: Service could not be found";
|
5
|
+
exports.MCP_SESSION_HEADER = "mcp-session-id";
|
6
|
+
exports.NEW_LINE = "\n";
|
@@ -0,0 +1,41 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.createMcpServer = createMcpServer;
|
4
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
5
|
+
const logger_1 = require("../logger");
|
6
|
+
const structures_1 = require("../annotations/structures");
|
7
|
+
const tools_1 = require("./tools");
|
8
|
+
const resources_1 = require("./resources");
|
9
|
+
const prompts_1 = require("./prompts");
|
10
|
+
function createMcpServer(config, annotations) {
|
11
|
+
logger_1.LOGGER.debug("Creating MCP server instance");
|
12
|
+
const server = new mcp_js_1.McpServer({
|
13
|
+
name: config.name,
|
14
|
+
version: config.version,
|
15
|
+
capabilities: config.capabilities,
|
16
|
+
});
|
17
|
+
if (!annotations)
|
18
|
+
return server;
|
19
|
+
logger_1.LOGGER.debug("Annotations found for server: ", annotations);
|
20
|
+
// TODO: Handle the parsed annotations
|
21
|
+
// TODO: Error handling
|
22
|
+
// TODO: This should only be mapped once, not per each server instance. Maybe this should be pre-packaged on load?
|
23
|
+
// TODO: Handle auth
|
24
|
+
for (const entry of annotations.values()) {
|
25
|
+
switch (entry.constructor) {
|
26
|
+
case structures_1.McpToolAnnotation:
|
27
|
+
(0, tools_1.assignToolToServer)(entry, server);
|
28
|
+
continue;
|
29
|
+
case structures_1.McpResourceAnnotation:
|
30
|
+
(0, resources_1.assignResourceToServer)(entry, server);
|
31
|
+
continue;
|
32
|
+
case structures_1.McpPromptAnnotation:
|
33
|
+
(0, prompts_1.assignPromptToServer)(entry, server);
|
34
|
+
continue;
|
35
|
+
default:
|
36
|
+
logger_1.LOGGER.warn("Invalid annotation entry - Cannot be parsed by MCP server, skipping...");
|
37
|
+
continue;
|
38
|
+
}
|
39
|
+
}
|
40
|
+
return server;
|
41
|
+
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.assignPromptToServer = assignPromptToServer;
|
4
|
+
const logger_1 = require("../logger");
|
5
|
+
const utils_1 = require("./utils");
|
6
|
+
/* @ts-ignore */
|
7
|
+
const cds = global.cds || require("@sap/cds"); // This is a work around for missing cds context
|
8
|
+
// NOTE: Not satisfied with below implementation, will need to be revised for full effect
|
9
|
+
/*
|
10
|
+
annotate CatalogService with @mcp.prompts: [{
|
11
|
+
name : 'give-me-book-abstract',
|
12
|
+
title : 'Book Abstract',
|
13
|
+
description: 'Gives an abstract of a book based on the title',
|
14
|
+
template : 'Search the internet and give me an abstract of the book {{book-id}}', = template
|
15
|
+
inputs : [{ Inputs = Args
|
16
|
+
key : 'book-id',
|
17
|
+
type: 'String'
|
18
|
+
}]
|
19
|
+
}];
|
20
|
+
*/
|
21
|
+
function assignPromptToServer(model, server) {
|
22
|
+
logger_1.LOGGER.debug("Adding prompt", model);
|
23
|
+
for (const prompt of model.prompts) {
|
24
|
+
const inputs = constructInputArgs(prompt.inputs);
|
25
|
+
server.registerPrompt(prompt.name, {
|
26
|
+
title: prompt.title,
|
27
|
+
description: prompt.description,
|
28
|
+
argsSchema: inputs,
|
29
|
+
}, async (args) => {
|
30
|
+
let parsedMsg = prompt.template;
|
31
|
+
for (const [k, v] of Object.entries(args)) {
|
32
|
+
parsedMsg = parsedMsg.replaceAll(`{{${k}}}`, String(v));
|
33
|
+
}
|
34
|
+
return {
|
35
|
+
messages: [
|
36
|
+
{
|
37
|
+
role: prompt.role,
|
38
|
+
content: {
|
39
|
+
type: "text",
|
40
|
+
text: parsedMsg,
|
41
|
+
},
|
42
|
+
},
|
43
|
+
],
|
44
|
+
};
|
45
|
+
});
|
46
|
+
}
|
47
|
+
}
|
48
|
+
function constructInputArgs(inputs) {
|
49
|
+
// Not happy with using any here, but zod types are hard to figure out....
|
50
|
+
if (!inputs || inputs.length <= 0)
|
51
|
+
return undefined;
|
52
|
+
const result = {};
|
53
|
+
for (const el of inputs) {
|
54
|
+
result[el.key] = (0, utils_1.determineMcpParameterType)(el.type);
|
55
|
+
}
|
56
|
+
return result;
|
57
|
+
}
|