@gavdi/cap-mcp 1.1.4 → 1.2.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/lib/annotations/utils.js +27 -24
- package/lib/auth/factory.js +1 -1
- package/lib/auth/handlers.js +5 -3
- package/lib/auth/utils.js +41 -9
- package/lib/auth/xsuaa-service.js +103 -15
- package/lib/mcp/session-manager.js +1 -1
- package/lib/mcp.js +3 -0
- package/package.json +4 -2
package/lib/annotations/utils.js
CHANGED
|
@@ -288,10 +288,12 @@ function parseCdsRestrictions(restrictions, requires) {
|
|
|
288
288
|
});
|
|
289
289
|
continue;
|
|
290
290
|
}
|
|
291
|
-
const mapped = el.to
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
291
|
+
const mapped = Array.isArray(el.to)
|
|
292
|
+
? el.to.map((to) => ({
|
|
293
|
+
role: to,
|
|
294
|
+
operations: ops,
|
|
295
|
+
}))
|
|
296
|
+
: [{ role: el.to, operations: ops }];
|
|
295
297
|
result.push(...mapped);
|
|
296
298
|
}
|
|
297
299
|
return result;
|
|
@@ -300,29 +302,30 @@ function parseCdsRestrictions(restrictions, requires) {
|
|
|
300
302
|
* Maps the "grant" property from CdsRestriction to McpRestriction
|
|
301
303
|
*/
|
|
302
304
|
function mapOperationRestriction(cdsRestrictions) {
|
|
303
|
-
const result = [];
|
|
304
305
|
if (!cdsRestrictions || cdsRestrictions.length <= 0) {
|
|
305
|
-
|
|
306
|
-
result.push("READ");
|
|
307
|
-
result.push("UPDATE");
|
|
308
|
-
result.push("DELETE");
|
|
309
|
-
return result;
|
|
306
|
+
return ["CREATE", "READ", "UPDATE", "DELETE"];
|
|
310
307
|
}
|
|
308
|
+
if (!Array.isArray(cdsRestrictions)) {
|
|
309
|
+
return translateOperationRestriction(cdsRestrictions);
|
|
310
|
+
}
|
|
311
|
+
const result = [];
|
|
311
312
|
for (const el of cdsRestrictions) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
case "*":
|
|
317
|
-
result.push("CREATE");
|
|
318
|
-
result.push("READ");
|
|
319
|
-
result.push("UPDATE");
|
|
320
|
-
result.push("DELETE");
|
|
321
|
-
continue;
|
|
322
|
-
default:
|
|
323
|
-
result.push(el);
|
|
324
|
-
continue;
|
|
325
|
-
}
|
|
313
|
+
const translated = translateOperationRestriction(el);
|
|
314
|
+
if (!translated || translated.length <= 0)
|
|
315
|
+
continue;
|
|
316
|
+
result.push(...translated);
|
|
326
317
|
}
|
|
327
318
|
return result;
|
|
328
319
|
}
|
|
320
|
+
function translateOperationRestriction(restrictionType) {
|
|
321
|
+
switch (restrictionType) {
|
|
322
|
+
case "CHANGE":
|
|
323
|
+
return ["UPDATE"];
|
|
324
|
+
case "WRITE":
|
|
325
|
+
return ["CREATE", "READ", "UPDATE", "DELETE"];
|
|
326
|
+
case "*":
|
|
327
|
+
return ["CREATE", "READ", "UPDATE", "DELETE"];
|
|
328
|
+
default:
|
|
329
|
+
return [restrictionType];
|
|
330
|
+
}
|
|
331
|
+
}
|
package/lib/auth/factory.js
CHANGED
|
@@ -47,7 +47,7 @@ function authHandlerFactory() {
|
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
49
|
// For XSUAA/JWT auth types, use @sap/xssec for validation
|
|
50
|
-
if ((authKind === "jwt" || authKind === "xsuaa") &&
|
|
50
|
+
if ((authKind === "jwt" || authKind === "xsuaa" || authKind === "ias") &&
|
|
51
51
|
xsuaaService.isConfigured()) {
|
|
52
52
|
const securityContext = await xsuaaService.createSecurityContext(req);
|
|
53
53
|
if (!securityContext) {
|
package/lib/auth/handlers.js
CHANGED
|
@@ -10,7 +10,7 @@ const logger_1 = require("../logger");
|
|
|
10
10
|
async function handleTokenRequest(req, res, xsuaaService) {
|
|
11
11
|
try {
|
|
12
12
|
const params = { ...req.query, ...req.body };
|
|
13
|
-
const { grant_type, code, redirect_uri, refresh_token } = params;
|
|
13
|
+
const { grant_type, code, redirect_uri, refresh_token, code_verifier } = params;
|
|
14
14
|
if (grant_type === "authorization_code") {
|
|
15
15
|
if (!code || !redirect_uri) {
|
|
16
16
|
res.status(400).json({
|
|
@@ -19,9 +19,11 @@ async function handleTokenRequest(req, res, xsuaaService) {
|
|
|
19
19
|
});
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
|
-
const tokenData = await xsuaaService.exchangeCodeForToken(code, redirect_uri);
|
|
22
|
+
const tokenData = await xsuaaService.exchangeCodeForToken(code, redirect_uri, code_verifier);
|
|
23
|
+
const scopedToken = await xsuaaService.getApplicationScopes(tokenData);
|
|
24
|
+
logger_1.LOGGER.debug("Scopes in token:", scopedToken.scope);
|
|
23
25
|
logger_1.LOGGER.debug("[AUTH] Token exchange successful");
|
|
24
|
-
res.json(
|
|
26
|
+
res.json(scopedToken);
|
|
25
27
|
}
|
|
26
28
|
else if (grant_type === "refresh_token") {
|
|
27
29
|
if (!refresh_token) {
|
package/lib/auth/utils.js
CHANGED
|
@@ -191,7 +191,7 @@ function configureOAuthProxy(expressApp) {
|
|
|
191
191
|
!credentials.url) {
|
|
192
192
|
throw new Error("Invalid security credentials");
|
|
193
193
|
}
|
|
194
|
-
registerOAuthEndpoints(expressApp, credentials);
|
|
194
|
+
registerOAuthEndpoints(expressApp, credentials, kind);
|
|
195
195
|
}
|
|
196
196
|
/**
|
|
197
197
|
* Determines the correct protocol (HTTP/HTTPS) for URL construction.
|
|
@@ -213,8 +213,10 @@ function getProtocol(req) {
|
|
|
213
213
|
* Registers OAuth endpoints for XSUAA integration
|
|
214
214
|
* Only called for jwt/xsuaa/ias auth types with valid credentials
|
|
215
215
|
*/
|
|
216
|
-
function registerOAuthEndpoints(expressApp, credentials) {
|
|
216
|
+
function registerOAuthEndpoints(expressApp, credentials, kind) {
|
|
217
217
|
const xsuaaService = new xsuaa_service_1.XSUAAService();
|
|
218
|
+
// Fetch endpoints from OIDC configuration
|
|
219
|
+
xsuaaService.discoverOAuthEndpoints();
|
|
218
220
|
// Add JSON and URL-encoded body parsing for OAuth endpoints
|
|
219
221
|
expressApp.use("/oauth", express_1.default.json());
|
|
220
222
|
expressApp.use("/oauth", express_1.default.urlencoded({ extended: true }));
|
|
@@ -231,17 +233,17 @@ function registerOAuthEndpoints(expressApp, credentials) {
|
|
|
231
233
|
}));
|
|
232
234
|
// OAuth Authorization endpoint - stateless redirect to XSUAA
|
|
233
235
|
expressApp.get("/oauth/authorize", (req, res) => {
|
|
234
|
-
const { state, redirect_uri } = req.query;
|
|
236
|
+
const { state, redirect_uri, client_id, code_challenge, code_challenge_method, scope, } = req.query;
|
|
235
237
|
// Client validation and redirect URI validation is handled by XSUAA
|
|
236
238
|
// We delegate all client management to XSUAA's built-in OAuth server
|
|
237
239
|
const protocol = getProtocol(req);
|
|
238
240
|
const redirectUri = redirect_uri || `${protocol}://${req.get("host")}/oauth/callback`;
|
|
239
|
-
const authUrl = xsuaaService.getAuthorizationUrl(redirectUri, state);
|
|
241
|
+
const authUrl = xsuaaService.getAuthorizationUrl(redirectUri, client_id ?? "", state, code_challenge, code_challenge_method, scope);
|
|
240
242
|
res.redirect(authUrl);
|
|
241
243
|
});
|
|
242
244
|
// OAuth Callback endpoint - stateless token exchange
|
|
243
245
|
expressApp.get("/oauth/callback", async (req, res) => {
|
|
244
|
-
const { code, state, error, error_description, redirect_uri } = req.query;
|
|
246
|
+
const { code, state, error, error_description, redirect_uri, code_verifier, } = req.query;
|
|
245
247
|
logger_1.LOGGER.debug("[AUTH] Callback received", code, state);
|
|
246
248
|
if (error) {
|
|
247
249
|
res.status(400).json({
|
|
@@ -260,8 +262,10 @@ function registerOAuthEndpoints(expressApp, credentials) {
|
|
|
260
262
|
try {
|
|
261
263
|
const protocol = getProtocol(req);
|
|
262
264
|
const url = redirect_uri || `${protocol}://${req.get("host")}/oauth/callback`;
|
|
263
|
-
const tokenData = await xsuaaService.exchangeCodeForToken(code, url);
|
|
264
|
-
|
|
265
|
+
const tokenData = await xsuaaService.exchangeCodeForToken(code, url, code_verifier);
|
|
266
|
+
const scopedToken = await xsuaaService.getApplicationScopes(tokenData);
|
|
267
|
+
logger_1.LOGGER.debug("Scopes in token:", scopedToken.scope);
|
|
268
|
+
res.json(scopedToken);
|
|
265
269
|
}
|
|
266
270
|
catch (error) {
|
|
267
271
|
logger_1.LOGGER.error("OAuth callback error:", error);
|
|
@@ -288,13 +292,27 @@ function registerOAuthEndpoints(expressApp, credentials) {
|
|
|
288
292
|
response_types_supported: ["code"],
|
|
289
293
|
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
290
294
|
code_challenge_methods_supported: ["S256"],
|
|
291
|
-
|
|
295
|
+
scopes_supported: ["openid"],
|
|
292
296
|
token_endpoint_auth_methods_supported: ["client_secret_post"],
|
|
293
297
|
registration_endpoint_auth_methods_supported: ["client_secret_basic"],
|
|
294
298
|
});
|
|
295
299
|
});
|
|
296
300
|
// OAuth Dynamic Client Registration discovery endpoint (GET)
|
|
297
301
|
expressApp.get("/oauth/register", async (req, res) => {
|
|
302
|
+
// IAS does not support DCR so we will respond with the pre-configured client_id
|
|
303
|
+
if (kind === "ias") {
|
|
304
|
+
const protocol = getProtocol(req);
|
|
305
|
+
const enhancedResponse = {
|
|
306
|
+
client_id: credentials.clientid, // Add our CAP app's client ID
|
|
307
|
+
redirect_uris: req.body.redirect_uris || [
|
|
308
|
+
`${protocol}://${req.get("host")}/oauth/callback`,
|
|
309
|
+
],
|
|
310
|
+
};
|
|
311
|
+
logger_1.LOGGER.debug("Provided static client_id during DCR registration process");
|
|
312
|
+
res.json(enhancedResponse);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
// Keep original implementation for XSUAA
|
|
298
316
|
try {
|
|
299
317
|
// Simple proxy for discovery - no CSRF needed
|
|
300
318
|
const response = await fetch(`${credentials.url}/oauth/register`, {
|
|
@@ -324,6 +342,20 @@ function registerOAuthEndpoints(expressApp, credentials) {
|
|
|
324
342
|
});
|
|
325
343
|
// OAuth Dynamic Client Registration endpoint (POST) with CSRF handling
|
|
326
344
|
expressApp.post("/oauth/register", async (req, res) => {
|
|
345
|
+
// IAS does not support DCR so we will respond with the pre-configured client_id
|
|
346
|
+
if (kind === "ias") {
|
|
347
|
+
const protocol = getProtocol(req);
|
|
348
|
+
const enhancedResponse = {
|
|
349
|
+
client_id: credentials.clientid, // Add our CAP app's client ID
|
|
350
|
+
redirect_uris: req.body.redirect_uris || [
|
|
351
|
+
`${protocol}://${req.get("host")}/oauth/callback`,
|
|
352
|
+
],
|
|
353
|
+
};
|
|
354
|
+
logger_1.LOGGER.debug("Provided static client_id during DCR registration process");
|
|
355
|
+
res.json(enhancedResponse);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
// Keep original implementation for XSUAA
|
|
327
359
|
try {
|
|
328
360
|
// Step 1: Fetch CSRF token from XSUAA
|
|
329
361
|
const csrfResponse = await fetch(`${credentials.url}/oauth/register`, {
|
|
@@ -361,7 +393,7 @@ function registerOAuthEndpoints(expressApp, credentials) {
|
|
|
361
393
|
const enhancedResponse = {
|
|
362
394
|
...xsuaaData, // Keep all XSUAA fields
|
|
363
395
|
client_id: credentials.clientid, // Add our CAP app's client ID
|
|
364
|
-
client_secret: credentials.clientsecret, // CAP app's client secret
|
|
396
|
+
// client_secret: credentials.clientsecret, // CAP app's client secret
|
|
365
397
|
redirect_uris: req.body.redirect_uris || [
|
|
366
398
|
`${protocol}://${req.get("host")}/oauth/callback`,
|
|
367
399
|
], // Use client's redirect URIs
|
|
@@ -45,47 +45,104 @@ const cds = global.cds || require("@sap/cds");
|
|
|
45
45
|
class XSUAAService {
|
|
46
46
|
credentials;
|
|
47
47
|
xsuaaService;
|
|
48
|
+
endpoints;
|
|
48
49
|
constructor() {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
// In case of IAS, the final token conversion to get application scopes has to be done with XSUAA, so there will be 2 sets of credentials
|
|
51
|
+
this.credentials = {
|
|
52
|
+
authProvider: cds.env.requires.auth?.credentials,
|
|
53
|
+
xsuaa: cds.env.requires.xsuaa?.credentials ||
|
|
54
|
+
cds.env.requires.auth?.credentials,
|
|
55
|
+
};
|
|
56
|
+
// Set default endpoints in case OIDC discovery call fails
|
|
57
|
+
this.endpoints = {
|
|
58
|
+
authProvider: {
|
|
59
|
+
discovery_url: `${this.credentials.authProvider?.url}/.well-known/openid-configuration`,
|
|
60
|
+
authorization_endpoint: `${this.credentials.authProvider?.url}/oauth/authorize`,
|
|
61
|
+
token_endpoint: `${this.credentials.authProvider?.url}/oauth/token`,
|
|
62
|
+
},
|
|
63
|
+
xsuaa: {
|
|
64
|
+
discovery_url: `${this.credentials.xsuaa?.url}/.well-known/openid-configuration`,
|
|
65
|
+
authorization_endpoint: `${this.credentials.xsuaa?.url}/oauth/authorize`,
|
|
66
|
+
token_endpoint: `${this.credentials.xsuaa?.url}/oauth/token`,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
this.xsuaaService = new xssec.XsuaaService(this.credentials.xsuaa);
|
|
52
70
|
}
|
|
53
71
|
isConfigured() {
|
|
54
|
-
return !!(this.credentials?.clientid &&
|
|
55
|
-
this.credentials?.clientsecret &&
|
|
56
|
-
this.credentials?.url);
|
|
72
|
+
return !!(this.credentials.authProvider?.clientid &&
|
|
73
|
+
this.credentials.authProvider?.clientsecret &&
|
|
74
|
+
this.credentials.authProvider?.url);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Fetch oauth endpoints from the OIDC discovery endpoints from both XSUAA (and IAS).
|
|
78
|
+
* If none found than the default will be used.
|
|
79
|
+
*/
|
|
80
|
+
async discoverOAuthEndpoints() {
|
|
81
|
+
// Do discovery for both 'authProvider' and 'xsuaa' endpoint sets
|
|
82
|
+
try {
|
|
83
|
+
const endpointKeys = Object.keys(this.endpoints);
|
|
84
|
+
for (let key of endpointKeys) {
|
|
85
|
+
const response = await fetch(this.endpoints[key].discovery_url, {
|
|
86
|
+
method: "GET",
|
|
87
|
+
headers: {
|
|
88
|
+
Accept: "application/json",
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
const errorData = await response.json();
|
|
93
|
+
logger_1.LOGGER.warn(`OAuth endpoints fetch failed: ${response.status} ${errorData.error_description || errorData.error}. Continuing with default configuration.`);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
const oidcConfig = await response.json();
|
|
97
|
+
this.endpoints[key].authorization_endpoint =
|
|
98
|
+
oidcConfig.authorization_endpoint;
|
|
99
|
+
this.endpoints[key].token_endpoint = oidcConfig.token_endpoint;
|
|
100
|
+
logger_1.LOGGER.debug(`OAuth endpoints for [${key}] set to:`, this.endpoints[key]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (error instanceof Error) {
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
throw new Error(`OAuth endpoints fetch failed: ${String(error)}`);
|
|
109
|
+
}
|
|
57
110
|
}
|
|
58
111
|
/**
|
|
59
112
|
* Generates authorization URL using @sap/xssec
|
|
60
113
|
*/
|
|
61
|
-
getAuthorizationUrl(redirectUri, state) {
|
|
114
|
+
getAuthorizationUrl(redirectUri, client_id, state, code_challenge, code_challenge_method, scope) {
|
|
62
115
|
const params = new URLSearchParams({
|
|
63
116
|
response_type: "code",
|
|
64
|
-
client_id: this.credentials.clientid,
|
|
65
117
|
redirect_uri: redirectUri,
|
|
66
118
|
// scope: "uaa.resource",
|
|
119
|
+
client_id,
|
|
120
|
+
...(!!code_challenge ? { code_challenge } : {}),
|
|
121
|
+
...(!!code_challenge_method ? { code_challenge_method } : {}),
|
|
122
|
+
...(!!scope ? { scope } : {}),
|
|
67
123
|
});
|
|
68
124
|
if (state) {
|
|
69
125
|
params.append("state", state);
|
|
70
126
|
}
|
|
71
|
-
return `${this.
|
|
127
|
+
return `${this.endpoints.authProvider.authorization_endpoint}?${params.toString()}`;
|
|
72
128
|
}
|
|
73
129
|
/**
|
|
74
130
|
* Exchange authorization code for token using @sap/xssec
|
|
75
131
|
*/
|
|
76
|
-
async exchangeCodeForToken(code, redirectUri) {
|
|
132
|
+
async exchangeCodeForToken(code, redirectUri, code_verifier) {
|
|
77
133
|
try {
|
|
78
134
|
const tokenOptions = {
|
|
79
135
|
grant_type: "authorization_code",
|
|
80
136
|
code,
|
|
81
137
|
redirect_uri: redirectUri,
|
|
138
|
+
...(!!code_verifier ? { code_verifier } : {}),
|
|
82
139
|
};
|
|
83
|
-
// Use direct XSUAA token endpoint for authorization code exchange
|
|
84
|
-
const response = await fetch(
|
|
140
|
+
// Use direct XSUAA/IAS token endpoint for authorization code exchange
|
|
141
|
+
const response = await fetch(this.endpoints.authProvider.token_endpoint, {
|
|
85
142
|
method: "POST",
|
|
86
143
|
headers: {
|
|
87
144
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
88
|
-
Authorization: `Basic ${Buffer.from(`${this.credentials.clientid}:${this.credentials.clientsecret}`).toString("base64")}`,
|
|
145
|
+
Authorization: `Basic ${Buffer.from(`${this.credentials.authProvider?.clientid}:${this.credentials.authProvider?.clientsecret}`).toString("base64")}`,
|
|
89
146
|
},
|
|
90
147
|
body: new URLSearchParams(tokenOptions),
|
|
91
148
|
});
|
|
@@ -102,16 +159,47 @@ class XSUAAService {
|
|
|
102
159
|
throw new Error(`Token exchange failed: ${String(error)}`);
|
|
103
160
|
}
|
|
104
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Convert access_token from authorization code into access_token with application scopes
|
|
164
|
+
*/
|
|
165
|
+
async getApplicationScopes(token) {
|
|
166
|
+
try {
|
|
167
|
+
const tokenOptions = {
|
|
168
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
169
|
+
reponse_type: "token+id_token",
|
|
170
|
+
assertion: token.access_token,
|
|
171
|
+
};
|
|
172
|
+
const response = await fetch(this.endpoints.xsuaa.token_endpoint, {
|
|
173
|
+
method: "POST",
|
|
174
|
+
headers: {
|
|
175
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
176
|
+
Authorization: `Basic ${Buffer.from(`${this.credentials.xsuaa?.clientid}:${this.credentials.xsuaa?.clientsecret}`).toString("base64")}`,
|
|
177
|
+
},
|
|
178
|
+
body: new URLSearchParams(tokenOptions),
|
|
179
|
+
});
|
|
180
|
+
if (!response.ok) {
|
|
181
|
+
const errorData = (await response.json());
|
|
182
|
+
throw new Error(`Token exchange for scopes failed: ${response.status} ${errorData.error_description || errorData.error}`);
|
|
183
|
+
}
|
|
184
|
+
return response.json();
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
if (error instanceof Error) {
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
throw new Error(`Token exchange for scopes failed: ${String(error)}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
105
193
|
/**
|
|
106
194
|
* Refresh access token using @sap/xssec
|
|
107
195
|
*/
|
|
108
196
|
async refreshAccessToken(refreshToken) {
|
|
109
197
|
try {
|
|
110
|
-
const response = await fetch(
|
|
198
|
+
const response = await fetch(this.endpoints.xsuaa.token_endpoint, {
|
|
111
199
|
method: "POST",
|
|
112
200
|
headers: {
|
|
113
201
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
114
|
-
Authorization: `Basic ${Buffer.from(`${this.credentials.clientid}:${this.credentials.clientsecret}`).toString("base64")}`,
|
|
202
|
+
Authorization: `Basic ${Buffer.from(`${this.credentials.xsuaa?.clientid}:${this.credentials.xsuaa?.clientsecret}`).toString("base64")}`,
|
|
115
203
|
},
|
|
116
204
|
body: new URLSearchParams({
|
|
117
205
|
grant_type: "refresh_token",
|
|
@@ -70,7 +70,7 @@ class McpSessionManager {
|
|
|
70
70
|
sessionIdGenerator: () => (0, crypto_1.randomUUID)(),
|
|
71
71
|
enableJsonResponse: enableJson,
|
|
72
72
|
onsessioninitialized: (sid) => {
|
|
73
|
-
logger_1.LOGGER.
|
|
73
|
+
logger_1.LOGGER.info("Session initialized with ID: ", sid);
|
|
74
74
|
logger_1.LOGGER.debug("Transport mode", { enableJsonResponse: enableJson });
|
|
75
75
|
this.sessions.set(sid, {
|
|
76
76
|
server: server,
|
package/lib/mcp.js
CHANGED
|
@@ -13,6 +13,7 @@ const loader_1 = require("./config/loader");
|
|
|
13
13
|
const session_manager_1 = require("./mcp/session-manager");
|
|
14
14
|
const utils_2 = require("./auth/utils");
|
|
15
15
|
const helmet_1 = __importDefault(require("helmet"));
|
|
16
|
+
const cors_1 = __importDefault(require("cors"));
|
|
16
17
|
/* @ts-ignore */
|
|
17
18
|
const cds = global.cds; // Use hosting app's CDS instance exclusively
|
|
18
19
|
/**
|
|
@@ -41,6 +42,8 @@ class McpPlugin {
|
|
|
41
42
|
logger_1.LOGGER.debug("Event received for 'bootstrap'");
|
|
42
43
|
this.expressApp = app;
|
|
43
44
|
this.expressApp.use("/mcp", express_1.default.json());
|
|
45
|
+
// Only needed to use MCP Inspector in local browser:
|
|
46
|
+
this.expressApp.use(["/oauth", "/.well-known"], (0, cors_1.default)({ origin: "http://localhost:6274" }));
|
|
44
47
|
// Apply helmet security middleware only to MCP routes
|
|
45
48
|
this.expressApp.use("/mcp", (0, helmet_1.default)({
|
|
46
49
|
contentSecurityPolicy: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gavdi/cap-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "MCP Pluging for CAP",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"MCP",
|
|
@@ -41,12 +41,13 @@
|
|
|
41
41
|
"release": "release-it"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"@sap/cds": "^9
|
|
44
|
+
"@sap/cds": "^9",
|
|
45
45
|
"express": "^4"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@modelcontextprotocol/sdk": "^1.19.1",
|
|
49
49
|
"@sap/xssec": "^4.9.1",
|
|
50
|
+
"cors": "^2.8.5",
|
|
50
51
|
"helmet": "^8.1.0",
|
|
51
52
|
"zod": "^3.25.67",
|
|
52
53
|
"zod-to-json-schema": "^3.24.5"
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"devDependencies": {
|
|
55
56
|
"@cap-js/cds-types": "^0.13.0",
|
|
56
57
|
"@release-it/conventional-changelog": "^10.0.1",
|
|
58
|
+
"@types/cors": "^2.8.19",
|
|
57
59
|
"@types/express": "^5.0.3",
|
|
58
60
|
"@types/jest": "^30.0.0",
|
|
59
61
|
"@types/node": "^24.0.3",
|