@gavdi/cap-mcp 1.5.1 → 1.6.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/README.md +1 -1
- package/lib/auth/utils.js +98 -97
- package/lib/mcp/utils.js +5 -5
- package/lib/mcp.js +9 -9
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ By integrating MCP with your CAP applications, you unlock:
|
|
|
29
29
|
|
|
30
30
|
- **Node.js**: Version 18 or higher
|
|
31
31
|
- **SAP CAP**: Version 9 or higher
|
|
32
|
-
- **Express**: Version
|
|
32
|
+
- **Express**: Version 5 or higher
|
|
33
33
|
- **TypeScript**: Optional but recommended
|
|
34
34
|
|
|
35
35
|
### Step 1: Install the Plugin
|
package/lib/auth/utils.js
CHANGED
|
@@ -304,111 +304,112 @@ function registerOAuthEndpoints(expressApp, credentials, kind) {
|
|
|
304
304
|
// OAuth Dynamic Client Registration discovery endpoint (GET)
|
|
305
305
|
expressApp.get("/oauth/register", async (req, res) => {
|
|
306
306
|
const baseUrl = (0, host_resolver_1.buildPublicBaseUrl)(req);
|
|
307
|
+
// XSUAA does not support DCR so we will respond with the pre-configured client_id
|
|
307
308
|
// IAS does not support DCR so we will respond with the pre-configured client_id
|
|
308
|
-
if (kind === "ias") {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
309
|
+
// if (kind === "ias") {
|
|
310
|
+
const enhancedResponse = {
|
|
311
|
+
client_id: credentials.clientid, // Add our CAP app's client ID
|
|
312
|
+
redirect_uris: req.body.redirect_uris || [`${baseUrl}/oauth/callback`],
|
|
313
|
+
};
|
|
314
|
+
logger_1.LOGGER.debug("Provided static client_id during DCR registration process");
|
|
315
|
+
res.json(enhancedResponse);
|
|
316
|
+
return;
|
|
317
|
+
// }
|
|
319
318
|
// Keep original implementation for XSUAA
|
|
320
|
-
try {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
319
|
+
// try {
|
|
320
|
+
// // Simple proxy for discovery - no CSRF needed
|
|
321
|
+
// const response = await fetch(`${credentials.url}/oauth/register`, {
|
|
322
|
+
// method: "GET",
|
|
323
|
+
// headers: {
|
|
324
|
+
// Authorization: `Basic ${Buffer.from(`${credentials.clientid}:${credentials.clientsecret}`).toString("base64")}`,
|
|
325
|
+
// Accept: "application/json",
|
|
326
|
+
// },
|
|
327
|
+
// });
|
|
328
|
+
// const xsuaaData = await response.json();
|
|
329
|
+
// // Add missing required fields that MCP client expects
|
|
330
|
+
// const enhancedResponse = {
|
|
331
|
+
// ...xsuaaData, // Keep all XSUAA fields
|
|
332
|
+
// client_id: credentials.clientid, // Add our CAP app's client ID
|
|
333
|
+
// redirect_uris: [`${baseUrl}/oauth/callback`], // Add our callback URL for discovery
|
|
334
|
+
// };
|
|
335
|
+
// res.status(response.status).json(enhancedResponse);
|
|
336
|
+
// } catch (error) {
|
|
337
|
+
// LOGGER.error("OAuth registration discovery error:", error);
|
|
338
|
+
// res.status(500).json({
|
|
339
|
+
// error: "server_error",
|
|
340
|
+
// error_description:
|
|
341
|
+
// error instanceof Error ? error.message : "Unknown error",
|
|
342
|
+
// });
|
|
343
|
+
// }
|
|
345
344
|
});
|
|
346
345
|
// OAuth Dynamic Client Registration endpoint (POST) with CSRF handling
|
|
347
346
|
expressApp.post("/oauth/register", async (req, res) => {
|
|
348
347
|
const baseUrl = (0, host_resolver_1.buildPublicBaseUrl)(req);
|
|
348
|
+
// XSUAA does not support DCR so we will respond with the pre-configured client_id
|
|
349
349
|
// IAS does not support DCR so we will respond with the pre-configured client_id
|
|
350
|
-
if (kind === "ias") {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
350
|
+
// if (kind === "ias") {
|
|
351
|
+
const enhancedResponse = {
|
|
352
|
+
client_id: credentials.clientid, // Add our CAP app's client ID
|
|
353
|
+
redirect_uris: req.body.redirect_uris || [`${baseUrl}/oauth/callback`],
|
|
354
|
+
};
|
|
355
|
+
logger_1.LOGGER.debug("Provided static client_id during DCR registration process");
|
|
356
|
+
res.json(enhancedResponse);
|
|
357
|
+
return;
|
|
358
|
+
// }
|
|
361
359
|
// Keep original implementation for XSUAA
|
|
362
|
-
try {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
360
|
+
// try {
|
|
361
|
+
// Step 1: Fetch CSRF token from XSUAA
|
|
362
|
+
// const csrfResponse = await fetch(`${credentials.url}/oauth/register`, {
|
|
363
|
+
// method: "GET",
|
|
364
|
+
// headers: {
|
|
365
|
+
// "X-CSRF-Token": "Fetch",
|
|
366
|
+
// Authorization: `Basic ${Buffer.from(`${credentials.clientid}:${credentials.clientsecret}`).toString("base64")}`,
|
|
367
|
+
// Accept: "application/json",
|
|
368
|
+
// },
|
|
369
|
+
// });
|
|
370
|
+
// if (!csrfResponse.ok) {
|
|
371
|
+
// throw new Error(`CSRF fetch failed: ${csrfResponse.status}`);
|
|
372
|
+
// }
|
|
373
|
+
// Step 2: Extract CSRF token and session cookie
|
|
374
|
+
// const setCookieHeader = csrfResponse.headers.get("set-cookie") || "";
|
|
375
|
+
// const csrfToken = extractCsrfFromCookie(setCookieHeader);
|
|
376
|
+
// if (!csrfToken) {
|
|
377
|
+
// throw new Error("Could not extract CSRF token from XSUAA response");
|
|
378
|
+
// }
|
|
379
|
+
// Step 3: Make actual registration POST with CSRF token
|
|
380
|
+
// const registrationResponse = await fetch(
|
|
381
|
+
// `${credentials.url}/oauth/register`,
|
|
382
|
+
// {
|
|
383
|
+
// method: "POST",
|
|
384
|
+
// headers: {
|
|
385
|
+
// "Content-Type": "application/json",
|
|
386
|
+
// "X-CSRF-Token": csrfToken,
|
|
387
|
+
// Cookie: setCookieHeader,
|
|
388
|
+
// Authorization: `Basic ${Buffer.from(`${credentials.clientid}:${credentials.clientsecret}`).toString("base64")}`,
|
|
389
|
+
// Accept: "application/json",
|
|
390
|
+
// },
|
|
391
|
+
// body: JSON.stringify(req.body),
|
|
392
|
+
// },
|
|
393
|
+
// );
|
|
394
|
+
// const xsuaaData = await registrationResponse.json();
|
|
395
|
+
// Add missing required fields that MCP client expects
|
|
396
|
+
// const enhancedResponse = {
|
|
397
|
+
// ...xsuaaData, // Keep all XSUAA fields
|
|
398
|
+
// client_id: credentials.clientid, // Add our CAP app's client ID
|
|
399
|
+
// redirect_uris: req.body.redirect_uris || [
|
|
400
|
+
// `${baseUrl}/oauth/callback`,
|
|
401
|
+
// ],
|
|
402
|
+
// };
|
|
403
|
+
// LOGGER.debug("[AUTH] Register POST response", enhancedResponse);
|
|
404
|
+
// res.status(registrationResponse.status).json(enhancedResponse);
|
|
405
|
+
// } catch (error) {
|
|
406
|
+
// LOGGER.error("OAuth registration error:", error);
|
|
407
|
+
// res.status(500).json({
|
|
408
|
+
// error: "server_error",
|
|
409
|
+
// error_description:
|
|
410
|
+
// error instanceof Error ? error.message : "Unknown error",
|
|
411
|
+
// });
|
|
412
|
+
// }
|
|
412
413
|
});
|
|
413
414
|
logger_1.LOGGER.debug("OAuth endpoints registered for XSUAA integration");
|
|
414
415
|
}
|
package/lib/mcp/utils.js
CHANGED
|
@@ -195,14 +195,14 @@ function buildDeepInsertZodType(targetEntityName) {
|
|
|
195
195
|
async function handleMcpSessionRequest(req, res, sessions) {
|
|
196
196
|
const sessionIdHeader = req.headers[constants_1.MCP_SESSION_HEADER];
|
|
197
197
|
if (!sessionIdHeader || !sessions.has(sessionIdHeader)) {
|
|
198
|
-
res.status(
|
|
198
|
+
res.status(404).json({
|
|
199
|
+
jsonrpc: "2.0",
|
|
200
|
+
error: { code: -32001, message: "Session not found" },
|
|
201
|
+
id: null,
|
|
202
|
+
});
|
|
199
203
|
return;
|
|
200
204
|
}
|
|
201
205
|
const session = sessions.get(sessionIdHeader);
|
|
202
|
-
if (!session) {
|
|
203
|
-
res.status(400).send("Invalid session");
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
206
|
await session.transport.handleRequest(req, res);
|
|
207
207
|
}
|
|
208
208
|
/**
|
package/lib/mcp.js
CHANGED
|
@@ -104,13 +104,13 @@ class McpPlugin {
|
|
|
104
104
|
const sessionIdHeader = req.headers[constants_1.MCP_SESSION_HEADER];
|
|
105
105
|
const sessions = this.sessionManager.getSessions();
|
|
106
106
|
if (!sessionIdHeader || !sessions.has(sessionIdHeader)) {
|
|
107
|
-
return res.status(
|
|
107
|
+
return res.status(404).json({
|
|
108
108
|
jsonrpc: "2.0",
|
|
109
109
|
error: {
|
|
110
|
-
code: -
|
|
111
|
-
message: "
|
|
112
|
-
id: null,
|
|
110
|
+
code: -32001,
|
|
111
|
+
message: "Session not found",
|
|
113
112
|
},
|
|
113
|
+
id: null,
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
116
|
const session = sessions.get(sessionIdHeader);
|
|
@@ -139,13 +139,13 @@ class McpPlugin {
|
|
|
139
139
|
: this.sessionManager.getSession(sessionIdHeader);
|
|
140
140
|
if (!session) {
|
|
141
141
|
logger_1.LOGGER.error("Invalid session ID", sessionIdHeader);
|
|
142
|
-
res.status(
|
|
142
|
+
res.status(404).json({
|
|
143
143
|
jsonrpc: "2.0",
|
|
144
144
|
error: {
|
|
145
|
-
code: -
|
|
146
|
-
message: "
|
|
147
|
-
id: null,
|
|
145
|
+
code: -32001,
|
|
146
|
+
message: "Session not found",
|
|
148
147
|
},
|
|
148
|
+
id: null,
|
|
149
149
|
});
|
|
150
150
|
return;
|
|
151
151
|
}
|
|
@@ -163,8 +163,8 @@ class McpPlugin {
|
|
|
163
163
|
error: {
|
|
164
164
|
code: -32603,
|
|
165
165
|
message: "Internal Error: Transport failed",
|
|
166
|
-
id: null,
|
|
167
166
|
},
|
|
167
|
+
id: null,
|
|
168
168
|
});
|
|
169
169
|
return;
|
|
170
170
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gavdi/cap-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "MCP Plugin for CAP",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"MCP",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@sap/cds": ">=9",
|
|
47
|
-
"express": "^
|
|
47
|
+
"express": "^5"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@modelcontextprotocol/sdk": "^1.23.0",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"@types/cors": "^2.8.19",
|
|
61
61
|
"@types/express": "^5.0.3",
|
|
62
62
|
"@types/jest": "^30.0.0",
|
|
63
|
-
"@types/node": "^
|
|
63
|
+
"@types/node": "^25.3.0",
|
|
64
64
|
"@types/sinon": "^17.0.4",
|
|
65
65
|
"@types/supertest": "^6.0.2",
|
|
66
66
|
"docsify-cli": "^4.4.4",
|