@gavdi/cap-mcp 0.9.1 → 0.9.3

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 CHANGED
@@ -23,15 +23,10 @@ By integrating MCP with your CAP applications, you unlock:
23
23
 
24
24
  ## ⚠️ Development Status
25
25
 
26
- **This plugin is currently in active development (v0.9.0) and is not ready for production use.**
27
- APIs and annotations may change in future releases. Use in development and testing environments only.
26
+ **This plugin is currently in active development (v0.9.2) and approaching production readiness.**
27
+ APIs and annotations may change in future releases. Authentication and security features are implemented and tested.
28
28
 
29
- Version 1.0 of the plugin is planned to release shortly after auth integration is complete.
30
-
31
- ### 👾 Known Bugs
32
-
33
- > ❗Currently there is a bug in the `@modelcontextprotocol/sdk` package that breaks the RFC template strings. The issue has been reported.
34
- > Until this issue is fixed, the resource reads for dynamic queries are only possible if all query options are provided.
29
+ Version 1.0 of the plugin is scheduled for release in Summer 2025 after final stability testing and documentation completion.
35
30
 
36
31
  ## 📦 Installation
37
32
 
@@ -41,6 +36,78 @@ npm install @gavdi/cap-mcp
41
36
 
42
37
  The plugin follows CAP's standard plugin architecture and will automatically integrate with your CAP application.
43
38
 
39
+ ## 🚀 Quick Setup
40
+
41
+ ### Prerequisites
42
+
43
+ - **Node.js**: Version 18 or higher
44
+ - **SAP CAP**: Version 9 or higher
45
+ - **Express**: Version 4 or higher
46
+ - **TypeScript**: Optional but recommended
47
+
48
+ ### Step 1: Install the Plugin
49
+
50
+ ```bash
51
+ npm install @gavdi/cap-mcp
52
+ ```
53
+
54
+ ### Step 2: Configure Your CAP Application
55
+
56
+ Add MCP configuration to your `package.json`:
57
+
58
+ ```json
59
+ {
60
+ "cds": {
61
+ "mcp": {
62
+ "name": "my-bookshop-mcp",
63
+ "auth": "inherit"
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ ### Step 3: Add MCP Annotations
70
+
71
+ Annotate your CAP services with `@mcp` annotations:
72
+
73
+ ```cds
74
+ // srv/catalog-service.cds
75
+ service CatalogService {
76
+
77
+ @mcp: {
78
+ name: 'books',
79
+ description: 'Book catalog with search and filtering',
80
+ resource: ['filter', 'orderby', 'select', 'top', 'skip']
81
+ }
82
+ entity Books as projection on my.Books;
83
+
84
+ @mcp: {
85
+ name: 'get-book-recommendations',
86
+ description: 'Get personalized book recommendations',
87
+ tool: true
88
+ }
89
+ function getRecommendations(genre: String, limit: Integer) returns array of String;
90
+ }
91
+ ```
92
+
93
+ ### Step 4: Start Your Application
94
+
95
+ ```bash
96
+ cds serve
97
+ ```
98
+
99
+ The MCP server will be available at:
100
+ - **MCP Endpoint**: `http://localhost:4004/mcp`
101
+ - **Health Check**: `http://localhost:4004/mcp/health`
102
+
103
+ ### Step 5: Test with MCP Inspector
104
+
105
+ ```bash
106
+ npx @modelcontextprotocol/inspector
107
+ ```
108
+
109
+ Connect to `http://localhost:4004/mcp` to explore your generated MCP resources, tools, and prompts.
110
+
44
111
  ## 🎯 Features
45
112
 
46
113
  This plugin transforms your annotated CAP services into a fully functional MCP server that can be consumed by any MCP-compatible AI client.
@@ -142,10 +209,97 @@ annotate CatalogService with @mcp.prompts: [{
142
209
 
143
210
  ## 🔧 Configuration
144
211
 
212
+ ### Plugin Configuration
213
+
214
+ Configure the MCP plugin through your CAP application's `package.json` or `.cdsrc` file:
215
+
216
+ ```json
217
+ {
218
+ "cds": {
219
+ "mcp": {
220
+ "name": "my-mcp-server",
221
+ "version": "1.0.0",
222
+ "auth": "inherit",
223
+ "capabilities": {
224
+ "resources": {
225
+ "listChanged": true,
226
+ "subscribe": false
227
+ },
228
+ "tools": {
229
+ "listChanged": true
230
+ },
231
+ "prompts": {
232
+ "listChanged": true
233
+ }
234
+ }
235
+ }
236
+ }
237
+ }
238
+ ```
239
+
240
+ ### Configuration Options
241
+
242
+ | Option | Type | Default | Description |
243
+ |--------|------|---------|-------------|
244
+ | `name` | string | package.json name | MCP server name |
245
+ | `version` | string | package.json version | MCP server version |
246
+ | `auth` | `"inherit"` \| `"none"` | `"inherit"` | Authentication mode |
247
+ | `capabilities.resources.listChanged` | boolean | `true` | Enable resource list change notifications |
248
+ | `capabilities.resources.subscribe` | boolean | `false` | Enable resource subscriptions |
249
+ | `capabilities.tools.listChanged` | boolean | `true` | Enable tool list change notifications |
250
+ | `capabilities.prompts.listChanged` | boolean | `true` | Enable prompt list change notifications |
251
+
252
+ ### Authentication Configuration
253
+
254
+ The plugin supports two authentication modes:
255
+
256
+ #### `"inherit"` Mode (Default)
257
+ Uses your CAP application's existing authentication system:
258
+
259
+ ```json
260
+ {
261
+ "cds": {
262
+ "mcp": {
263
+ "auth": "inherit"
264
+ },
265
+ "requires": {
266
+ "auth": {
267
+ "kind": "xsuaa"
268
+ }
269
+ }
270
+ }
271
+ }
272
+ ```
273
+
274
+ #### `"none"` Mode (Development/Testing)
275
+ Disables authentication completely:
276
+
277
+ ```json
278
+ {
279
+ "cds": {
280
+ "mcp": {
281
+ "auth": "none"
282
+ }
283
+ }
284
+ }
285
+ ```
286
+
287
+ **⚠️ Security Warning**: Only use `"none"` mode in development environments. Never deploy to production without proper authentication.
288
+
289
+ #### Authentication Flow
290
+ 1. MCP client connects to `/mcp` endpoint
291
+ 2. CAP authentication middleware validates credentials (if `auth: "inherit"`)
292
+ 3. MCP session established with authenticated user context
293
+ 4. All MCP operations (resources, tools, prompts) inherit the authenticated user's permissions
294
+
295
+ ### Automatic Features
296
+
145
297
  The plugin automatically:
146
298
  - Scans your CAP service definitions for `@mcp` annotations
147
299
  - Generates appropriate MCP resources, tools, and prompts
148
300
  - Creates ResourceTemplates with proper OData v4 query parameter support
301
+ - Sets up HTTP endpoints at `/mcp` and `/mcp/health`
302
+ - Manages MCP session lifecycle and cleanup
149
303
 
150
304
  ## 🌟 Example AI Interactions
151
305
 
@@ -233,11 +387,13 @@ Would you like me to help you review any of these in detail?"
233
387
  - **Integration Ready**: Works with existing CAP-based workflow systems
234
388
  - **Mobile Friendly**: Access approvals from any MCP-compatible AI client
235
389
 
236
- ## 🧰 Testing Locally
390
+ ## 🧰 Development & Testing
391
+
392
+ ### Testing Your MCP Implementation
237
393
 
238
394
  If you want to test the MCP implementation you have made on your CAP application locally, you have 2 options available (that does not involve direct integration with AI Agent).
239
395
 
240
- ### Option #1 - MCP Inspector
396
+ #### Option #1 - MCP Inspector
241
397
 
242
398
  You can inspect the MCP implementation by utilizing the official `@modelcontextprotocol/inspector`.
243
399
 
@@ -247,10 +403,28 @@ For plugin implementation implementation in your own project it is recommended t
247
403
 
248
404
  For more information on the inspector, please [see the official documentation](https://github.com/modelcontextprotocol/inspector).
249
405
 
250
- ### Option #2 - Bruno Collection
406
+ #### Option #2 - Bruno Collection
251
407
 
252
408
  This repository comes with a Bruno collection available that includes some example queries you can use to verify your MCP implementation. These can be found in the `bruno` directory.
253
409
 
410
+ #### Option #3 - Automated Testing
411
+
412
+ Run the comprehensive test suite to validate your implementation:
413
+
414
+ ```bash
415
+ # Test specific components
416
+ npm test -- --testPathPattern=annotations # Test annotation parsing
417
+ npm test -- --testPathPattern=mcp # Test MCP functionality
418
+ npm test -- --testPathPattern=security # Test security boundaries
419
+ npm test -- --testPathPattern=auth # Test authentication
420
+
421
+ # Run with detailed output
422
+ npm test -- --verbose
423
+
424
+ # Run in watch mode for development
425
+ npm test -- --watch
426
+ ```
427
+
254
428
  ## 🤝 Contributing
255
429
 
256
430
  Contributions are welcome! This is an open-source project aimed at bridging CAP applications with the AI ecosystem.
@@ -264,11 +438,90 @@ Contributions are welcome! This is an open-source project aimed at bridging CAP
264
438
 
265
439
  This project is licensed under the Apache-2.0 License - see the [LICENSE.md](LICENSE.md) file for details.
266
440
 
441
+ ## 🔧 Troubleshooting
442
+
443
+ ### Common Issues
444
+
445
+ #### MCP Server Not Starting
446
+ - **Check Port Availability**: Ensure port 4004 is not in use by another process
447
+ - **Verify CAP Service**: Make sure your CAP application starts successfully with `cds serve`
448
+ - **Authentication Issues**: If using `auth: "inherit"`, ensure your CAP authentication is properly configured
449
+
450
+ #### MCP Client Connection Failures
451
+ ```bash
452
+ # Check if MCP endpoint is accessible
453
+ curl http://localhost:4004/mcp/health
454
+
455
+ # Expected response:
456
+ # {"status": "healthy", "timestamp": "2025-01-XX..."}
457
+ ```
458
+
459
+ #### Annotation Not Working
460
+ - **Syntax Check**: Verify your `@mcp` annotation syntax matches the examples
461
+ - **Service Deployment**: Ensure annotated entities/functions are properly deployed
462
+ - **Case Sensitivity**: Check that annotation properties use correct casing (`resource`, `tool`, `prompts`)
463
+
464
+ #### OData Query Issues
465
+ - **SDK Bug Workaround**: Due to the known `@modelcontextprotocol/sdk` bug, provide all query parameters when using dynamic queries
466
+ - **Parameter Validation**: Ensure query parameters match OData v4 syntax
467
+
468
+ #### Performance Issues
469
+ - **Resource Filtering**: Use specific `resource` arrays instead of `true` for large datasets
470
+ - **Query Optimization**: Implement proper database indexes for frequently queried fields
471
+
472
+ ### Debugging
473
+
474
+ #### Enable Debug Logging
475
+ ```json
476
+ {
477
+ "cds": {
478
+ "log": {
479
+ "levels": {
480
+ "mcp": "debug"
481
+ }
482
+ }
483
+ }
484
+ }
485
+ ```
486
+
487
+ #### Test MCP Implementation
488
+ ```bash
489
+ # Use MCP Inspector for interactive testing
490
+ npm run inspect
491
+
492
+ # Or run integration tests
493
+ npm test -- --testPathPattern=integration
494
+ ```
495
+
496
+ ### Getting Help
497
+
498
+ - **GitHub Issues**: Report bugs at [gavdilabs/cap-mcp-plugin](https://github.com/gavdilabs/cap-mcp-plugin/issues)
499
+ - **Documentation**: Check [MCP Specification](https://modelcontextprotocol.io) for protocol details
500
+ - **CAP Support**: Refer to [SAP CAP Documentation](https://cap.cloud.sap) for CAP-specific issues
501
+
502
+ ## 🚨 Performance & Limitations
503
+
504
+ ### Known Limitations
505
+ - **SDK Bug**: Dynamic resource queries require all query parameters due to `@modelcontextprotocol/sdk` RFC template string issue
506
+ - **Authentication Inheritance**: MCP authentication is tightly coupled to CAP's authentication system
507
+ - **Session Cleanup**: MCP sessions are cleaned up on HTTP connection close, not on explicit disconnect
508
+
509
+ ### Performance Considerations
510
+ - **Large Datasets**: Use `resource: ['top']` or similar constraints for entities with many records
511
+ - **Complex Queries**: OData query parsing adds overhead - consider caching for frequently accessed data
512
+ - **Concurrent Sessions**: Each MCP client creates a separate session - monitor memory usage with many clients
513
+
514
+ ### Scale Recommendations
515
+ - **Development**: No specific limits
516
+ - **Production**: Test with expected concurrent MCP client count
517
+ - **Enterprise**: Consider load balancing for high-availability scenarios
518
+
267
519
  ## 🔗 Resources
268
520
 
269
521
  - [Model Context Protocol Specification](https://modelcontextprotocol.io)
270
522
  - [SAP CAP Documentation](https://cap.cloud.sap)
271
523
  - [OData v4 Specification](https://odata.org)
524
+ - [MCP Inspector Tool](https://github.com/modelcontextprotocol/inspector)
272
525
 
273
526
  ---
274
527
  (c) Copyright by Gavdi Labs 2025 - All Rights Reserved
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.authHandlerFactory = authHandlerFactory;
4
+ exports.errorHandlerFactory = errorHandlerFactory;
5
+ /** JSON-RPC 2.0 error code for unauthorized requests */
6
+ const RPC_UNAUTHORIZED = 10;
7
+ /* @ts-ignore */
8
+ const cds = global.cds || require("@sap/cds"); // This is a work around for missing cds context
9
+ /**
10
+ * Creates an Express middleware for MCP authentication validation.
11
+ *
12
+ * This handler validates that requests are properly authenticated based on the CAP authentication
13
+ * configuration. It checks for authorization headers (except for 'dummy' auth), validates the
14
+ * CAP context, and ensures a valid user is present.
15
+ *
16
+ * The middleware performs the following validations:
17
+ * 1. Checks for Authorization header (unless CAP auth is 'dummy')
18
+ * 2. Validates that CAP context is properly initialized
19
+ * 3. Ensures an authenticated user exists and is not anonymous
20
+ *
21
+ * @returns Express RequestHandler middleware function
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const authMiddleware = authHandlerFactory();
26
+ * app.use('/mcp', authMiddleware);
27
+ * ```
28
+ *
29
+ * @throws {401} When authorization header is missing (non-dummy auth)
30
+ * @throws {401} When user is not authenticated or is anonymous
31
+ * @throws {500} When CAP context is not properly loaded
32
+ */
33
+ function authHandlerFactory() {
34
+ const authKind = cds.env.requires.auth.kind;
35
+ return (req, res, next) => {
36
+ if (!req.headers.authorization && authKind !== "dummy") {
37
+ res.status(401).json({
38
+ jsonrpc: "2.0",
39
+ error: {
40
+ code: RPC_UNAUTHORIZED,
41
+ message: "Unauthorized",
42
+ id: null,
43
+ },
44
+ });
45
+ return;
46
+ }
47
+ const ctx = cds.context;
48
+ if (!ctx) {
49
+ res.status(500).json({
50
+ jsonrpc: "2.0",
51
+ error: {
52
+ code: -32603,
53
+ message: "Internal Error: Context not correctly loaded",
54
+ id: null,
55
+ },
56
+ });
57
+ return;
58
+ }
59
+ const user = ctx.user;
60
+ if (!user || user === cds.User.anonymous) {
61
+ res.status(401).json({
62
+ jsonrpc: "2.0",
63
+ error: {
64
+ code: RPC_UNAUTHORIZED,
65
+ message: "Unauthorized",
66
+ id: null,
67
+ },
68
+ });
69
+ return;
70
+ }
71
+ return next();
72
+ };
73
+ }
74
+ /**
75
+ * Creates an Express error handling middleware for CAP authentication errors.
76
+ *
77
+ * This error handler catches authentication and authorization errors thrown by CAP
78
+ * middleware and converts them to JSON-RPC 2.0 compliant error responses. It handles
79
+ * both 401 (Unauthorized) and 403 (Forbidden) errors specifically.
80
+ *
81
+ * @returns Express ErrorRequestHandler middleware function
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * const errorHandler = errorHandlerFactory();
86
+ * app.use('/mcp', errorHandler);
87
+ * ```
88
+ *
89
+ * @param err - The error object, expected to be 401 or 403 for auth errors
90
+ * @param req - Express request object (unused, marked with underscore)
91
+ * @param res - Express response object for sending error responses
92
+ * @param next - Express next function for passing unhandled errors
93
+ */
94
+ function errorHandlerFactory() {
95
+ return (err, _, res, next) => {
96
+ if (err === 401 || err === 403) {
97
+ res.status(err).json({
98
+ jsonrpc: "2.0",
99
+ error: {
100
+ code: RPC_UNAUTHORIZED,
101
+ message: err === 401 ? "Unauthorized" : "Forbidden",
102
+ id: null,
103
+ },
104
+ });
105
+ return;
106
+ }
107
+ next(err);
108
+ };
109
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isAuthEnabled = isAuthEnabled;
4
+ exports.getAccessRights = getAccessRights;
5
+ exports.registerAuthMiddleware = registerAuthMiddleware;
6
+ const handler_1 = require("./handler");
7
+ /**
8
+ * @fileoverview Authentication utilities for MCP-CAP integration.
9
+ *
10
+ * This module provides utilities for integrating CAP authentication with MCP servers.
11
+ * It supports all standard CAP authentication types and provides functions for:
12
+ * - Determining authentication status
13
+ * - Managing user access rights
14
+ * - Registering authentication middleware
15
+ *
16
+ * Supported CAP authentication types:
17
+ * - 'dummy': No authentication (privileged access)
18
+ * - 'mocked': Mock users with predefined credentials
19
+ * - 'basic': HTTP Basic Authentication
20
+ * - 'jwt': Generic JWT token validation
21
+ * - 'xsuaa': SAP BTP XSUAA OAuth2/JWT authentication
22
+ * - 'ias': SAP Identity Authentication Service
23
+ * - Custom string types for user-defined authentication strategies
24
+ *
25
+ * Access CAP auth configuration via: cds.env.requires.auth.kind
26
+ */
27
+ /* @ts-ignore */
28
+ const cds = global.cds || require("@sap/cds"); // This is a work around for missing cds context
29
+ /**
30
+ * Determines whether authentication is enabled for the MCP plugin.
31
+ *
32
+ * This function checks the plugin configuration to determine if authentication
33
+ * should be enforced. When authentication is disabled ('none'), the plugin
34
+ * operates with privileged access. For security reasons, this function defaults
35
+ * to enabling authentication unless explicitly disabled.
36
+ *
37
+ * @param configEnabled - The MCP authentication configuration type
38
+ * @returns true if authentication is enabled, false if disabled
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const authEnabled = isAuthEnabled('inherit'); // true
43
+ * const noAuth = isAuthEnabled('none'); // false
44
+ * ```
45
+ *
46
+ * @since 1.0.0
47
+ */
48
+ function isAuthEnabled(configEnabled) {
49
+ if (configEnabled === "none")
50
+ return false;
51
+ return true; // For now this will always default to true, as we do not want to falsely give access
52
+ }
53
+ /**
54
+ * Retrieves the appropriate user context for CAP service operations.
55
+ *
56
+ * This function returns the correct user context based on whether authentication
57
+ * is enabled. When authentication is enabled, it uses the current authenticated
58
+ * user from the CAP context. When disabled, it provides privileged access.
59
+ *
60
+ * The returned User object is used for:
61
+ * - Authorization checks in CAP services
62
+ * - Audit logging and traceability
63
+ * - Row-level security and data filtering
64
+ *
65
+ * @param authEnabled - Whether authentication is currently enabled
66
+ * @returns CAP User object with appropriate access rights
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * const user = getAccessRights(true); // Returns cds.context.user
71
+ * const admin = getAccessRights(false); // Returns cds.User.privileged
72
+ *
73
+ * // Use in CAP service calls
74
+ * const result = await service.tx({ user }).run(query);
75
+ * ```
76
+ *
77
+ * @throws {Error} When authentication is enabled but no user context exists
78
+ * @since 1.0.0
79
+ */
80
+ function getAccessRights(authEnabled) {
81
+ return authEnabled ? cds.context.user : cds.User.privileged;
82
+ }
83
+ /**
84
+ * Registers comprehensive authentication middleware for MCP endpoints.
85
+ *
86
+ * This function sets up the complete authentication middleware chain for MCP endpoints.
87
+ * It integrates with CAP's authentication system by:
88
+ *
89
+ * 1. Applying all CAP 'before' middleware (including auth middleware)
90
+ * 2. Adding error handling for authentication failures
91
+ * 3. Adding MCP-specific authentication validation
92
+ *
93
+ * The middleware chain handles all CAP authentication types automatically and
94
+ * converts authentication errors to JSON-RPC 2.0 compliant responses.
95
+ *
96
+ * Middleware execution order:
97
+ * 1. CAP middleware chain (authentication, logging, etc.)
98
+ * 2. Authentication error handler
99
+ * 3. MCP authentication validator
100
+ *
101
+ * @param expressApp - Express application instance to register middleware on
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * const app = express();
106
+ * registerAuthMiddleware(app);
107
+ *
108
+ * // Now all /mcp routes are protected with CAP authentication
109
+ * app.post('/mcp', mcpHandler);
110
+ * ```
111
+ *
112
+ * @throws {Error} When CAP middleware chain is not properly initialized
113
+ * @since 1.0.0
114
+ */
115
+ function registerAuthMiddleware(expressApp) {
116
+ const middlewares = cds.middlewares.before; // No types exists for this part of the CDS library
117
+ // Build array of auth middleware to apply
118
+ const authMiddleware = [];
119
+ // Add CAP middleware
120
+ middlewares.forEach((mw) => {
121
+ const process = mw.factory();
122
+ if (process && process.length > 0) {
123
+ authMiddleware.push(process);
124
+ }
125
+ });
126
+ // Add MCP auth middleware
127
+ authMiddleware.push((0, handler_1.errorHandlerFactory)());
128
+ authMiddleware.push((0, handler_1.authHandlerFactory)());
129
+ // Apply auth middleware to all /mcp routes EXCEPT health
130
+ expressApp?.use(/^\/mcp(?!\/health).*/, ...authMiddleware);
131
+ }
@@ -12,6 +12,7 @@ const logger_1 = require("../logger");
12
12
  const CAPConfigurationSchema = zod_1.z.object({
13
13
  name: zod_1.z.string(),
14
14
  version: zod_1.z.string(),
15
+ auth: zod_1.z.custom(),
15
16
  capabilities: zod_1.z.object({
16
17
  tools: zod_1.z.object({
17
18
  listChanged: zod_1.z.boolean().optional(),
@@ -22,6 +22,7 @@ function loadConfiguration() {
22
22
  return {
23
23
  name: cdsEnv?.name ?? packageInfo.name,
24
24
  version: cdsEnv?.version ?? packageInfo.version,
25
+ auth: cdsEnv?.auth ?? "inherit",
25
26
  capabilities: {
26
27
  tools: cdsEnv?.capabilities?.tools ?? { listChanged: true },
27
28
  resources: cdsEnv?.capabilities?.resources ?? { listChanged: true },
@@ -7,6 +7,7 @@ const structures_1 = require("../annotations/structures");
7
7
  const tools_1 = require("./tools");
8
8
  const resources_1 = require("./resources");
9
9
  const prompts_1 = require("./prompts");
10
+ const utils_1 = require("../auth/utils");
10
11
  /**
11
12
  * Creates and configures an MCP server instance with the given configuration and annotations
12
13
  * @param config - CAP configuration object
@@ -25,14 +26,14 @@ function createMcpServer(config, annotations) {
25
26
  return server;
26
27
  }
27
28
  logger_1.LOGGER.debug("Annotations found for server: ", annotations);
28
- // TODO: Handle auth
29
+ const authEnabled = (0, utils_1.isAuthEnabled)(config.auth);
29
30
  for (const entry of annotations.values()) {
30
31
  if (entry instanceof structures_1.McpToolAnnotation) {
31
- (0, tools_1.assignToolToServer)(entry, server);
32
+ (0, tools_1.assignToolToServer)(entry, server, authEnabled);
32
33
  continue;
33
34
  }
34
35
  else if (entry instanceof structures_1.McpResourceAnnotation) {
35
- (0, resources_1.assignResourceToServer)(entry, server);
36
+ (0, resources_1.assignResourceToServer)(entry, server, authEnabled);
36
37
  continue;
37
38
  }
38
39
  else if (entry instanceof structures_1.McpPromptAnnotation) {
@@ -5,6 +5,7 @@ const custom_resource_template_1 = require("./custom-resource-template");
5
5
  const logger_1 = require("../logger");
6
6
  const utils_1 = require("./utils");
7
7
  const validation_1 = require("./validation");
8
+ const utils_2 = require("../auth/utils");
8
9
  // import cds from "@sap/cds";
9
10
  /* @ts-ignore */
10
11
  const cds = global.cds || require("@sap/cds"); // This is a work around for missing cds context
@@ -14,10 +15,10 @@ const cds = global.cds || require("@sap/cds"); // This is a work around for miss
14
15
  * @param model - The resource annotation containing entity metadata and query options
15
16
  * @param server - The MCP server instance to register the resource with
16
17
  */
17
- function assignResourceToServer(model, server) {
18
+ function assignResourceToServer(model, server, authEnabled) {
18
19
  logger_1.LOGGER.debug("Adding resource", model);
19
20
  if (model.functionalities.size <= 0) {
20
- registerStaticResource(model, server);
21
+ registerStaticResource(model, server, authEnabled);
21
22
  return;
22
23
  }
23
24
  // Dynamic resource registration
@@ -83,7 +84,8 @@ function assignResourceToServer(model, server) {
83
84
  };
84
85
  }
85
86
  try {
86
- const response = await service.run(query);
87
+ const accessRights = (0, utils_2.getAccessRights)(authEnabled);
88
+ const response = await service.tx({ user: accessRights }).run(query);
87
89
  return {
88
90
  contents: [
89
91
  {
@@ -112,7 +114,7 @@ function assignResourceToServer(model, server) {
112
114
  * @param model - The resource annotation with entity metadata
113
115
  * @param server - The MCP server instance to register with
114
116
  */
115
- function registerStaticResource(model, server) {
117
+ function registerStaticResource(model, server, authEnabled) {
116
118
  server.registerResource(model.name, `odata://${model.serviceName}/${model.name}`, { title: model.target, description: model.description }, async (uri, extra) => {
117
119
  const queryParameters = extra;
118
120
  const service = cds.services[model.serviceName];
@@ -122,7 +124,8 @@ function registerStaticResource(model, server) {
122
124
  const query = SELECT.from(model.target).limit(queryParameters.top
123
125
  ? validator.validateTop(queryParameters.top)
124
126
  : 100);
125
- const response = await service.run(query);
127
+ const accessRights = (0, utils_2.getAccessRights)(authEnabled);
128
+ const response = await service.tx({ user: accessRights }).run(query);
126
129
  return {
127
130
  contents: [
128
131
  {
package/lib/mcp/tools.js CHANGED
@@ -5,6 +5,7 @@ const utils_1 = require("./utils");
5
5
  const logger_1 = require("../logger");
6
6
  const constants_1 = require("./constants");
7
7
  const zod_1 = require("zod");
8
+ const utils_2 = require("../auth/utils");
8
9
  /* @ts-ignore */
9
10
  const cds = global.cds || require("@sap/cds"); // This is a work around for missing cds context
10
11
  /**
@@ -13,15 +14,15 @@ const cds = global.cds || require("@sap/cds"); // This is a work around for miss
13
14
  * @param model - The tool annotation containing operation metadata and parameters
14
15
  * @param server - The MCP server instance to register the tool with
15
16
  */
16
- function assignToolToServer(model, server) {
17
+ function assignToolToServer(model, server, authEnabled) {
17
18
  logger_1.LOGGER.debug("Adding tool", model);
18
19
  const parameters = buildToolParameters(model.parameters);
19
20
  if (model.entityKey) {
20
21
  // Assign tool as bound operation
21
- assignBoundOperation(parameters, model, server);
22
+ assignBoundOperation(parameters, model, server, authEnabled);
22
23
  return;
23
24
  }
24
- assignUnboundOperation(parameters, model, server);
25
+ assignUnboundOperation(parameters, model, server, authEnabled);
25
26
  }
26
27
  /**
27
28
  * Registers a bound operation that operates on a specific entity instance
@@ -30,7 +31,7 @@ function assignToolToServer(model, server) {
30
31
  * @param model - Tool annotation with bound operation metadata
31
32
  * @param server - MCP server instance to register with
32
33
  */
33
- function assignBoundOperation(params, model, server) {
34
+ function assignBoundOperation(params, model, server, authEnabled) {
34
35
  if (!model.keyTypeMap || model.keyTypeMap.size <= 0) {
35
36
  logger_1.LOGGER.error("Invalid tool assignment - missing key map for bound operation");
36
37
  throw new Error("Bound operation cannot be assigned to tool list, missing keys");
@@ -65,7 +66,8 @@ function assignBoundOperation(params, model, server) {
65
66
  continue;
66
67
  operationInput[k] = v;
67
68
  }
68
- const response = await service.send({
69
+ const accessRights = (0, utils_2.getAccessRights)(authEnabled);
70
+ const response = await service.tx({ user: accessRights }).send({
69
71
  event: model.target,
70
72
  entity: model.entityKey,
71
73
  data: operationInput,
@@ -73,8 +75,11 @@ function assignBoundOperation(params, model, server) {
73
75
  });
74
76
  return {
75
77
  content: Array.isArray(response)
76
- ? response.map((el) => ({ type: "text", text: String(el) }))
77
- : [{ type: "text", text: String(response) }],
78
+ ? response.map((el) => ({
79
+ type: "text",
80
+ text: formatResponseValue(el),
81
+ }))
82
+ : [{ type: "text", text: formatResponseValue(response) }],
78
83
  };
79
84
  });
80
85
  }
@@ -85,7 +90,7 @@ function assignBoundOperation(params, model, server) {
85
90
  * @param model - Tool annotation with unbound operation metadata
86
91
  * @param server - MCP server instance to register with
87
92
  */
88
- function assignUnboundOperation(params, model, server) {
93
+ function assignUnboundOperation(params, model, server, authEnabled) {
89
94
  const inputSchema = buildZodSchema(params);
90
95
  server.registerTool(model.name, {
91
96
  title: model.name,
@@ -105,11 +110,17 @@ function assignUnboundOperation(params, model, server) {
105
110
  ],
106
111
  };
107
112
  }
108
- const response = await service.send(model.target, args);
113
+ const accessRights = (0, utils_2.getAccessRights)(authEnabled);
114
+ const response = await service
115
+ .tx({ user: accessRights })
116
+ .send(model.target, args);
109
117
  return {
110
118
  content: Array.isArray(response)
111
- ? response.map((el) => ({ type: "text", text: String(el) }))
112
- : [{ type: "text", text: String(response) }],
119
+ ? response.map((el) => ({
120
+ type: "text",
121
+ text: formatResponseValue(el),
122
+ }))
123
+ : [{ type: "text", text: formatResponseValue(response) }],
113
124
  };
114
125
  });
115
126
  }
@@ -127,6 +138,27 @@ function buildToolParameters(params) {
127
138
  }
128
139
  return result;
129
140
  }
141
+ /**
142
+ * Converts a value to a string representation suitable for MCP responses
143
+ * Handles objects and arrays by JSON stringifying them instead of using String()
144
+ * @param value - The value to convert to string
145
+ * @returns String representation of the value
146
+ */
147
+ function formatResponseValue(value) {
148
+ if (value === null || value === undefined) {
149
+ return String(value);
150
+ }
151
+ if (typeof value === "object") {
152
+ try {
153
+ return JSON.stringify(value, null, 2);
154
+ }
155
+ catch (error) {
156
+ // Fallback to String() if JSON.stringify fails (e.g., circular references)
157
+ return String(value);
158
+ }
159
+ }
160
+ return String(value);
161
+ }
130
162
  /**
131
163
  * Constructs a complete Zod schema object for MCP tool input validation
132
164
  * @param params - Record of parameter names to Zod schema types
package/lib/mcp.js CHANGED
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const cds_1 = __importDefault(require("@sap/cds"));
6
7
  const logger_1 = require("./logger");
7
8
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
8
9
  const express_1 = __importDefault(require("express"));
@@ -11,6 +12,9 @@ const utils_1 = require("./mcp/utils");
11
12
  const constants_1 = require("./mcp/constants");
12
13
  const loader_1 = require("./config/loader");
13
14
  const session_manager_1 = require("./mcp/session-manager");
15
+ const utils_2 = require("./auth/utils");
16
+ /* @ts-ignore */
17
+ // const cds = global.cds || require("@sap/cds"); // This is a work around for missing cds context
14
18
  // TODO: Handle auth
15
19
  /**
16
20
  * Main MCP plugin class that integrates CAP services with Model Context Protocol
@@ -38,6 +42,9 @@ class McpPlugin {
38
42
  logger_1.LOGGER.debug("Event received for 'bootstrap'");
39
43
  this.expressApp = app;
40
44
  this.expressApp.use(express_1.default.json());
45
+ if (this.config.auth === "inherit") {
46
+ (0, utils_2.registerAuthMiddleware)(this.expressApp);
47
+ }
41
48
  await this.registerApiEndpoints();
42
49
  logger_1.LOGGER.debug("Bootstrap complete");
43
50
  }
@@ -76,7 +83,6 @@ class McpPlugin {
76
83
  status: "UP",
77
84
  });
78
85
  });
79
- logger_1.LOGGER.debug("TESTING - Annotations", this.annotations);
80
86
  this.registerMcpSessionRoute();
81
87
  this.expressApp?.get("/mcp", (req, res) => (0, utils_1.handleMcpSessionRequest)(req, res, this.sessionManager.getSessions()));
82
88
  this.expressApp?.delete("/mcp", (req, res) => (0, utils_1.handleMcpSessionRequest)(req, res, this.sessionManager.getSessions()));
@@ -88,6 +94,7 @@ class McpPlugin {
88
94
  registerMcpSessionRoute() {
89
95
  logger_1.LOGGER.debug("Registering MCP entry point");
90
96
  this.expressApp?.post("/mcp", async (req, res) => {
97
+ logger_1.LOGGER.debug("CONTEXT", cds_1.default.context); // TODO: Remove this line after testing
91
98
  const sessionIdHeader = req.headers[constants_1.MCP_SESSION_HEADER];
92
99
  logger_1.LOGGER.debug("MCP request received", {
93
100
  hasSessionId: !!sessionIdHeader,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gavdi/cap-mcp",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "MCP Pluging for CAP",
5
5
  "keywords": [
6
6
  "MCP",