A CDS plugin to trim string attributes automatically
npm install @neoimpulse/cap-js-mcpThis plugin for the SAP Cloud Application Programming Model (CAP) provides a Model Context Protocol (MCP) server implementation. It enables AI assistants and other clients to interact with CAP applications through standardized tools, prompts, and resources.
- MCP Tools: Product catalog management with CRUD operations
- MCP Prompts: Template-based prompt system (extensible)
- MCP Resources: File and data resource access (extensible)
- Authentication: Configurable API key authentication
- Logging: Comprehensive request/response logging
To install the plugin, add it to your CAP project.
``sh`
npm add @neoimpulse/cap-js-mcp
Add the MCP configuration to your package.json:
`json`
{
"cds": {
"requires": {
"cap-js-mcp": {
"apiKey": "YOUR_SECRET_API_KEY",
"server": {
"name": "cap-mcp-server",
"version": "1.0.0"
},
"components": {
"tools": {
"path": "./srv/lib/mcp-tools"
},
"prompts": {
"path": "./srv/lib/mcp-prompts"
},
"resources": {
"path": "./srv/lib/mcp-resources"
}
},
"authentication": true,
"logging": true
}
}
}
}
The plugin includes a generic implementation based on the official SAP CAP MCP Server. These tools work with any CAP application and use the compiled CDS model from the running application.
#### How It Works
The default tools leverage CAP's runtime model (cds.model) which is automatically compiled when your CAP application starts. This means:
- ✅ No project path needed - Tools use the running application's model
- ✅ Automatic model updates - Changes to your CDS files are reflected in the model
- ✅ Service-aware - Automatically detects services and their endpoints
- ✅ Universal - Works with any CAP project structure
#### Default Tools
1. search_model - Search for CDS definitions (entities, services, actions, etc.)
- Fuzzy search by name
- Filter by kind (entity, service, action, etc.)
- Returns full CSN definitions with annotations and metadata
2. search_docs - Search CAP documentation
- Searches code snippets and examples
- Returns relevant documentation sections
- (Note: Currently a placeholder - full implementation would use vector embeddings)
3. get_cds_model - Get the complete CDS model
- Returns all services, entities, and definitions
- Optional filtering of built-in types
- Includes namespace and i18n information
4. get_service_endpoints - Get HTTP endpoints
- Lists all OData and custom endpoints
- Shows exposed entities per service
- Optional filtering by service name
5. analyze_entity - Deep entity analysis
- Element types and properties
- Associations and compositions
- Keys and annotations
- Relationship mapping
6. execute_odata_query - Execute OData queries
- Full OData v4 support ($filter, $select, $expand, etc.)
- Direct query execution on entities
- Service-aware query routing
- Returns formatted JSON results
#### Using Default Tools with Extensions
You can extend the default tools with your custom implementations using the extends configuration:
`json`
{
"cds": {
"requires": {
"cap-js-mcp": {
"components": {
"tools": {
"path": "./srv/lib/mcp-tools-example",
"extends": "./srv/lib/mcp-tools-default"
}
}
}
}
}
}
How the Extension Mechanism Works:
1. Base Class Loading: First loads the base class from extends pathpath
2. Main Class Loading: Then loads your custom class from
3. Tool Merging: Combines tools from both classes intelligently:
- ✅ Inherit: Base tools not present in main class are added
- 🔄 Override: Main class tools replace base tools with same name
- 📦 Combine: Both sets of tools are available in the final instance
Example Scenario:
`javascript
// Base class (mcp-tools-default.js) provides:
- search_model
- search_docs
- get_cds_model
- analyze_entity
- execute_odata_query
// Your class (mcp-tools-example.js) provides:
- get_products
- create_product
- search_model (custom implementation)
// Result after merging:
- search_model (from YOUR class - overridden)
- search_docs (inherited from base)
- get_cds_model (inherited from base)
- analyze_entity (inherited from base)
- execute_odata_query (inherited from base)
- get_products (from YOUR class)
- create_product (from YOUR class)
`
Console Output During Loading:
``
🔧 Loading tools from: ./srv/lib/mcp-tools-example
⬆️ Extends: ./srv/lib/mcp-tools-default
📚 Loading base class from: ./srv/lib/mcp-tools-default
✅ Base class loaded successfully
✅ Main class loaded successfully
🔗 Inheriting tool: search_docs
🔗 Inheriting tool: get_cds_model
🔗 Inheriting tool: analyze_entity
🔗 Inheriting tool: execute_odata_query
🔄 Overriding tool: search_model
✅ tools loaded successfully with inheritance
📊 Total tools: 7
Benefits:
- ✅ No Code Duplication: Keep generic CAP functionality from base
- ✅ Selective Override: Replace only specific tools you need to customize
- ✅ Composition: Add domain-specific tools alongside generic ones
- ✅ Maintainability: Base updates don't break your custom code
#### Example: Using Default Tools
`javascript
// Search for all entities in the model
const entities = await mcpClient.callTool("search_model", {
kind: "entity",
topN: 10
});
// Find a specific service by name
const service = await mcpClient.callTool("search_model", {
name: "CatalogService",
kind: "service",
topN: 1
});
// Analyze an entity structure
const bookAnalysis = await mcpClient.callTool("analyze_entity", {
entityName: "CatalogService.Books"
});
// Get all service endpoints
const endpoints = await mcpClient.callTool("get_service_endpoints", {});
// Execute an OData query
const books = await mcpClient.callTool("execute_odata_query", {
entityName: "CatalogService.Books",
filter: "stock > 0",
select: "ID,title,price",
orderby: "price desc",
top: 10
});
// Execute query with expand
const booksWithAuthor = await mcpClient.callTool("execute_odata_query", {
entityName: "CatalogService.Books",
select: "ID,title,price",
expand: "author",
filter: "price < 30"
});
`
---
⚠️ Note: The built-in product catalog tools are for demonstration and testing purposes only. They use in-memory data storage and are not intended for production use. In a real application, you would implement tools that interact with your actual CAP services and data models.
The MCP server comes with built-in product catalog management tools:
- get_products - Get all products from the catalogget_product
- - Get a single product by ID with full detailscreate_product
- - Create a single productcreate_products
- - Create multiple productschange_product
- - Update a single productchange_products
- - Update multiple productsremove_product
- - Remove a single product by IDremove_products
- - Remove multiple products by IDs
`javascript
// Get all products from the catalog
const products = await mcpClient.callTool("get_products", {});
// Get a specific product by ID
const product = await mcpClient.callTool("get_product", { id: 1 });
// Create a new product
const newProduct = await mcpClient.callTool("create_product", {
name: "MacBook Air M3",
price: 1299.99,
inStock: true,
category: "Laptops",
description: "Apple MacBook Air with M3 chip"
});
// Create multiple products at once
const multipleProducts = await mcpClient.callTool("create_products", {
products: [
{
name: "iPad Pro",
price: 999.99,
inStock: true,
category: "Tablets",
description: "iPad Pro with M2 chip"
},
{
name: "AirPods Pro",
price: 279.99,
inStock: false,
category: "Audio",
description: "Wireless earbuds with noise cancellation"
}
]
});
// Update a single product
const updatedProduct = await mcpClient.callTool("change_product", {
id: 1,
price: 2799.99,
inStock: false
});
// Update multiple products
const updatedProducts = await mcpClient.callTool("change_products", {
products: [
{ id: 2, inStock: true },
{ id: 5, price: 299.99 }
]
});
// Remove a single product
const removedProduct = await mcpClient.callTool("remove_product", { id: 3 });
// Remove multiple products
const removedProducts = await mcpClient.callTool("remove_products", {
ids: [4, 5]
});
`
The plugin automatically registers as a CDS plugin and exposes MCP endpoints:
1. Tools: Executable functions that perform operations
2. Prompts: Template-based text generation (extensible)
3. Resources: Access to files and data resources (extensible)
The server includes sample product data for testing:
- MacBook Pro 16 (€2999.00)
- Dell XPS 13 (€1299.99)
- iPhone 15 Pro (€1199.00)
- Samsung Galaxy S24 (€899.99)
- Sony WH-1000XM5 (€349.99)
Important: This demo data is stored in memory and will be reset when the server restarts. For production use, replace the demo tools with implementations that connect to your actual CAP services and persistent data storage.
``
srv/
├── lib/
│ ├── mcp-tools-default.js # Generic CAP tools (base implementation)
│ ├── mcp-tools-example.js # Demo product management tools (extends default)
│ ├── mcp-prompts-example.js # Prompt templates (extensible)
│ └── mcp-resources-example.js # Resource handlers (extensible)
└── mcp-server.js # Main MCP server with extension loader
`
┌─────────────────────────────────────────────────────────────┐
│ cds-plugin.js │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ loadComponentWithInheritance() │ │
│ │ │ │
│ │ 1. Load Base Class (if extends is configured) │ │
│ │ ↓ │ │
│ │ 2. Instantiate Base → baseInstance │ │
│ │ ↓ │ │
│ │ 3. Load Main Class │ │
│ │ ↓ │ │
│ │ 4. Instantiate Main → mainInstance │ │
│ │ ↓ │ │
│ │ 5. Merge: baseInstance.tools → mainInstance.tools │ │
│ │ • Inherit: Tools not in main │ │
│ │ • Override: Tools in both (main wins) │ │
│ │ ↓ │ │
│ │ 6. Return merged mainInstance │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────┐ ┌─────────────────────┐
│ mcp-tools-default │ │ mcp-tools-example │
├─────────────────────┤ ├─────────────────────┤
│ • search_model │ │ • get_products │
│ • search_docs │ ←─ │ • get_product │
│ • get_cds_model │ extends │ • create_product │
│ • analyze_entity │ │ • change_product │
│ • execute_odata_query│ │ • remove_product │
│ • get_service_... │ │ (+ inherits all →) │
└─────────────────────┘ └─────────────────────┘
↓ ↓
└────────────────┬───────────┘
↓
┌───────────────────────┐
│ Merged Instance │
├───────────────────────┤
│ All 11+ tools │
│ available to clients │
└───────────────────────┘
`
`json`
// package.json
{
"cds": {
"requires": {
"cap-js-mcp": {
"components": {
"tools": {
"path": "./srv/lib/mcp-tools-example",
"extends": "./srv/lib/mcp-tools-default" ← Extension config
}
}
}
}
}
}
Loading Sequence:
1. Bootstrap: cds.on("bootstrap") triggeredloadComponents()
2. Load Components: calledextends
3. Extension Check: Reads from config
4. Base Loading: Requires and instantiates base class
5. Main Loading: Requires and instantiates main class
6. Merging: Combines tools/prompts/resources intelligently
7. Registration: Registers all tools with MCP server
8. Ready: Server starts handling requests
#### mcp-tools-default.js - Generic CAP Tools
This is the core implementation inspired by the official @cap-js/mcp-server. It provides:
Key Features:
- Runtime Model Access: Uses cds.model from the running CAP application
- Fuzzy Search: Modified Levenshtein distance algorithm for flexible name matching
- Endpoint Discovery: Automatically maps services to HTTP endpoints
- OData Query Execution: Direct query execution with full OData v4 support
- Service Awareness: Detects and connects to CAP services
Technical Implementation:
`javascript`
class MCPToolsDefault {
async _getModel() {
// Uses cds.model if available (runtime)
// Falls back to compiling from project if needed
return cds.model;
}
_fuzzyTopN(searchTerm, list, n) {
// Levenshtein distance with:
// - 0.5 cost for insertions/deletions
// - 1.0 cost for substitutions
// - 0 cost for exact matches
}
_addEndpointInformation(compiled, serviceInfo) {
// Enriches definitions with HTTP endpoints
// Maps OData paths to entities
// Handles auto-exposed entities
}
}
#### mcp-tools-example.js - Demo Tools
Example implementation showing how to create custom domain-specific tools:
- In-memory data storage (for demo purposes)
- CRUD operations on product catalog
- Type-safe schemas with validation
#### Extension Pattern
The plugin supports composition over inheritance through a sophisticated merging mechanism:
Configuration-Based Extension:
`json`
{
"tools": {
"path": "./srv/lib/my-tools",
"extends": "./srv/lib/mcp-tools-default"
}
}
How It Works Internally:
`javascript
// In cds-plugin.js - loadComponentWithInheritance()
function loadComponentWithInheritance(componentType, config, defaultPath) {
// 1. Load main component class
const MainComponentClass = require(config.path);
// 2. If extends is specified, load base class
if (config.extends) {
const BaseComponentClass = require(config.extends);
const baseInstance = new BaseComponentClass();
const mainInstance = new MainComponentClass();
// 3. Merge tools from base into main
for (const [toolName, toolDef] of baseInstance.tools.entries()) {
if (!mainInstance.tools.has(toolName)) {
// Inherit: Tool not in main class
mainInstance.tools.set(toolName, toolDef);
} else {
// Override: Tool exists in main class
console.log(🔄 Overriding tool: ${toolName});`
}
}
return mainInstance;
}
// 4. No inheritance - just return main instance
return new MainComponentClass();
}
Extension Strategies:
1. Pure Extension (Recommended):
`javascript`
// Your custom-tools.js
class CustomTools {
constructor() {
this.tools = new Map();
this._initializeTools();
}
_initializeTools() {
// Only define YOUR custom tools
this.tools.set("custom_tool", { ... });
}
}
Result: Base tools + Your tools (no conflicts)
2. Selective Override:
`javascript`
// Your custom-tools.js
class CustomTools {
_initializeTools() {
// Override a specific tool
this.tools.set("search_model", {
// Your custom implementation
});
// Add new tools
this.tools.set("custom_tool", { ... });
}
}
search_model
Result: Your replaces base's, other base tools inherited
3. Full Override (Not Recommended):
`javascript`
// Your custom-tools.js extends base
class CustomTools extends MCPToolsDefault {
_initializeTools() {
super._initializeTools(); // Keep base tools
// Add your tools
this.tools.set("custom_tool", { ... });
}
}
Result: Same as configuration-based extension, but harder to maintain
Applies to All Components:
- ✅ Tools (tools.extends)prompts.extends
- ✅ Prompts ()resources.extends
- ✅ Resources ()
Multiple Levels of Extension:
`json`
{
"tools": {
"path": "./srv/lib/my-production-tools",
"extends": "./srv/lib/my-base-tools"
}
}my-base-tools
Where could also extend another base:`javascript`
// my-base-tools.js
class MyBaseTools extends MCPToolsDefault { ... }
The best practice is to use the default tools for generic CAP operations and add your own domain-specific tools:
`json`
{
"cds": {
"requires": {
"cap-js-mcp": {
"components": {
"tools": {
"path": "./srv/lib/my-business-tools",
"extends": "./srv/lib/mcp-tools-default"
}
}
}
}
}
}
Why this approach?
- ✅ Keep generic CAP functionality (model search, entity analysis)
- ✅ Add business-specific operations (order processing, inventory management)
- ✅ No need to reimplement standard features
- ✅ AI assistants get both CAP knowledge and domain knowledge
Check Health Endpoint:
`bash`
curl http://localhost:4004/mcp/health
Response shows loaded components:
`json`
{
"status": "healthy",
"available": {
"tools": [
"search_model",
"search_docs",
"get_cds_model",
"analyze_entity",
"execute_odata_query",
"get_products",
"create_product"
]
},
"config": {
"components": {
"tools": "./srv/lib/mcp-tools-example"
}
}
}
Server Console Output:
``
🔧 Loading tools from: ./srv/lib/mcp-tools-example
⬆️ Extends: ./srv/lib/mcp-tools-default
📚 Loading base class from: ./srv/lib/mcp-tools-default
✅ Base class loaded successfully
🔧 Initialized 5 default MCP tools
✅ Main class loaded successfully
🔧 Initialized 10 MCP tools
🔗 Inheriting tool: search_docs
🔗 Inheriting tool: get_cds_model
🔗 Inheriting tool: analyze_entity
🔗 Inheriting tool: execute_odata_query
✅ tools loaded successfully with inheritance
📊 Total tools: 15
Reload Components at Runtime:
`bash`
curl -X POST http://localhost:4004/mcp/reload \
-H "Authorization: Basic
This reloads all components without restarting the server - useful during development!
#### 1. Model Exploration for AI Assistants
Use default tools to help AI understand your data model:
`javascript
// AI: "What entities are in the catalog service?"
search_model({ kind: "entity", name: "catalog" })
// AI: "Show me the structure of the Books entity"
analyze_entity({ entityName: "CatalogService.Books" })
`
#### 2. Data Querying for AI Agents
Enable AI to query your data:
`javascript
// AI: "Show me expensive books"
execute_odata_query({
entityName: "CatalogService.Books",
filter: "price > 50",
orderby: "price desc"
})
// AI: "Find out-of-stock products"
execute_odata_query({
entityName: "ProductService.Products",
filter: "stock eq 0",
select: "ID,name,category"
})
`
#### 3. Custom Business Logic
Add domain-specific tools alongside defaults:
`javascript`
class BusinessTools extends MCPToolsDefault {
_initializeTools() {
super._initializeTools(); // Keep CAP tools
// Add custom business operations
this.tools.set("process_order", {
definition: { / ... / },
handler: async (args) => {
// Your business logic
const srv = await cds.connect.to('OrderService');
return await srv.send('processOrder', args);
}
});
}
}
You can replace the demo components with your own implementations by changing the component paths in your configuration:
`json`
{
"cds": {
"requires": {
"cap-js-mcp": {
"apiKey": "YOUR_SECRET_API_KEY",
"components": {
"tools": {
"path": "./srv/lib/my-custom-tools"
},
"prompts": {
"path": "./srv/lib/my-custom-prompts"
},
"resources": {
"path": "./srv/lib/my-custom-resources"
}
}
}
}
}
}
This allows you to:
- Replace demo tools with production-ready implementations that interact with your CAP services
- Add custom prompts for your specific business domain
- Provide custom resources like files, documents, or other data sources
Extend the MCPTools class to add your own tools:
`javascriptCustom result: ${args.param}
// In your custom tools file
class CustomTools extends MCPTools {
_initializeTools() {
super._initializeTools();
this.tools.set("my_custom_tool", {
definition: {
name: "my_custom_tool",
description: "My custom tool description",
inputSchema: {
type: "object",
properties: {
param: { type: "string" }
},
required: ["param"]
}
},
handler: async (args) => {
return ;`
}
});
}
}
Similar patterns can be used to extend prompts and resources functionality.
Q: Why aren't my custom tools showing up?
A: Check these points:
1. Verify your class exports correctly: module.exports = MyTools;tools
2. Check constructor initializes the Map: this.tools = new Map();getToolDefinitions()
3. Implement required methods: , executeToolHandler(), etc./mcp/health
4. Check console output during startup for loading errors
5. Visit to see what tools are actually loaded
Q: Can I use inheritance AND the extends configuration?
A: Yes! Both work together:
`javascript`
// my-tools.js - uses JavaScript inheritance
class MyTools extends MCPToolsDefault {
_initializeTools() {
super._initializeTools(); // Get base tools
this.tools.set("my_tool", { ... }); // Add yours
}
}`json`
// package.json - uses configuration extension
{
"tools": {
"path": "./srv/lib/my-tools",
"extends": "./srv/lib/some-other-base"
}
}my-tools
Result: inherits from MCPToolsDefault (code) AND some-other-base (config)
Q: How do I override only specific tools?
A: Define only the tools you want to override in your class:
`javascript`
class MyTools {
constructor() {
this.tools = new Map();
this._initializeTools();
}
_initializeTools() {
// Override search_model only
this.tools.set("search_model", {
definition: { / your definition / },
handler: async (args) => { / your logic / }
});
// Other base tools will be inherited automatically
}
}
Q: Can I extend multiple base classes?
A: Not directly, but you can chain extensions:
``
Base1 → Base2 → YourClassYourClass
Configure: extends Base2, and Base2 extends Base1 in code.
Q: Do I need to restart the server after changing my tools?
A: No! Use the reload endpoint:
`bash`
curl -X POST http://localhost:4004/mcp/reload \
-H "Authorization: Basic
Q: What happens if base and main have the same tool?
A: Main class wins (override behavior). You'll see in console:
``
🔄 Overriding tool: search_model
To connect your CAP.js MCP server to GitHub Copilot, add the server configuration to your MCP settings.
Create a file .vscode/mcp.json in your project root or workspace:
`json`
{
"servers": {
"catalog": {
"type": "http",
"url": "https://your-app-domain.cfapps.region.hana.ondemand.com/mcp",
"headers": {
"Authorization": "Basic
}
}
}
}
The authorization header uses Basic authentication with your API key:
`bash`Encode your API key (replace with your actual key)
echo -n "apiKey:YOUR_SECRET_API_KEY" | base64
Use the output as the Authorization header value: Basic
Once configured, you can test the MCP tools in GitHub Copilot:
``
@catalog Can you show me all available products?
``
@catalog Create a new laptop product with the name "ThinkPad X1" priced at €1899
This project is licensed under the MIT License. See the LICENSE file for more details.
`json
// No extension - standalone
{
"tools": {
"path": "./srv/lib/my-tools"
}
}
// Simple extension - inherit from default
{
"tools": {
"path": "./srv/lib/my-tools",
"extends": "./srv/lib/mcp-tools-default"
}
}
// Chain extension - multiple levels
{
"tools": {
"path": "./srv/lib/production-tools",
"extends": "./srv/lib/base-tools"
}
}
// where base-tools.js also extends another class
// All components support extension
{
"tools": {
"path": "./srv/lib/my-tools",
"extends": "./srv/lib/mcp-tools-default"
},
"prompts": {
"path": "./srv/lib/my-prompts",
"extends": "./srv/lib/base-prompts"
},
"resources": {
"path": "./srv/lib/my-resources",
"extends": "./srv/lib/base-resources"
}
}
`
| Scenario | Base Has | Main Has | Result | Logged As |
|----------|----------|----------|--------|-----------|
| Inherit | tool_a | - | tool_a from base | 🔗 Inheriting |tool_a
| Override | | tool_a | tool_a from main | 🔄 Overriding |tool_b
| Add New | - | | tool_b from main | ✨ (no log) |tool_a
| Combine | , tool_b | tool_c | All three | Mix of above |
The mcp-tools-default.js implementation is inspired by the official @cap-js/mcp-server and provides:
#### Fuzzy Search Algorithm
Uses a modified Levenshtein distance with weighted costs:
- Insertions/Deletions: 0.5 cost
- Substitutions: 1.0 cost
- Exact matches: 0 cost
- Substring matches: 0.1 cost
This allows flexible searching like:
- "book" → finds "Books", "Bookshop", "BookService""catalog"
- → finds "CatalogService", "ProductCatalog"
#### Model Compilation
The tools use CAP's model compilation chain:
1. cds.resolve() - Find all CDS filescds.load()
2. - Parse and load with docs/locationscds.compile.for.nodejs()
3. - Include drafts and effective typescds.compile.to.serviceinfo()
4. - Extract service metadata
#### OData Query Execution
Direct query execution using CDS Query Language (CQL):
- Translates OData parameters to CQL
- Service-aware routing (uses service instances if available)
- Fallback to local execution (cds.run())$filter
- Full support for: , $select, $expand, $orderby, $top, $skip, $count
#### Endpoint Discovery
Automatically enriches the model with HTTP endpoints:
- Detects OData services and their paths
- Maps entities to endpoints (e.g., /odata/v4/catalog/Books)
- Filters auto-exposed entities
- Handles draft entities and contained entities
| Feature | mcp-tools-default.js | mcp-tools-example.js |cds.model
|---------|------------------------|------------------------|
| Purpose | Generic CAP operations | Domain-specific demo |
| Data Source | CDS Model () | In-memory array |
| Production Ready | ✅ Yes | ❌ Demo only |
| Model Awareness | ✅ Full CSN access | ❌ No model integration |
| OData Support | ✅ Query execution | ❌ Not applicable |
| Service Integration | ✅ Connects to services | ❌ Standalone |
| Use Case | AI model exploration, data querying | Testing, examples |
Model Caching:
- The CDS model is compiled once and cached in cds.model
- Subsequent tool calls reuse the cached model
- Model updates are handled automatically by CAP
Query Optimization:
- Direct service connection when available
- Connection pooling managed by CAP
- Efficient fuzzy search with early termination
Memory Usage:
- Default tools: Minimal (uses existing cds.model`)
- Example tools: ~5KB for demo product data
Contributions are welcome! Please feel free to submit a Pull Request.
- CAP
- CDS
- MCP
- Model Context Protocol
- AI Assistant
- Tools
- Prompts
- Resources
Thanks to the SAP CAP community and the MCP specification contributors for their support and contributions.