@neoimpulse/cap-js-mcp
v1.0.6
Published
A CDS plugin to trim string attributes automatically
Readme
CAP.js MCP Server
This 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.
Features
- 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
Installation
To install the plugin, add it to your CAP project.
npm add @neoimpulse/cap-js-mcpConfiguration
Add the MCP configuration to your package.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
}
}
}
}Available Tools
Standard CAP Tools (mcp-tools-default.js)
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
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
search_docs- Search CAP documentation- Searches code snippets and examples
- Returns relevant documentation sections
- (Note: Currently a placeholder - full implementation would use vector embeddings)
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
get_service_endpoints- Get HTTP endpoints- Lists all OData and custom endpoints
- Shows exposed entities per service
- Optional filtering by service name
analyze_entity- Deep entity analysis- Element types and properties
- Associations and compositions
- Keys and annotations
- Relationship mapping
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:
{
"cds": {
"requires": {
"cap-js-mcp": {
"components": {
"tools": {
"path": "./srv/lib/mcp-tools-example",
"extends": "./srv/lib/mcp-tools-default"
}
}
}
}
}
}How the Extension Mechanism Works:
- Base Class Loading: First loads the base class from
extendspath - Main Class Loading: Then loads your custom class from
path - 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:
// 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: 7Benefits:
- ✅ 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
// 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"
});Product Management (Demo Tools)
⚠️ 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
Example Usage
// 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]
});How It Works
The plugin automatically registers as a CDS plugin and exposes MCP endpoints:
- Tools: Executable functions that perform operations
- Prompts: Template-based text generation (extensible)
- 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.
Architecture
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 loaderExtension Architecture
┌─────────────────────────────────────────────────────────────┐
│ 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 │
└───────────────────────┘Configuration Flow
// 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:
- Bootstrap:
cds.on("bootstrap")triggered - Load Components:
loadComponents()called - Extension Check: Reads
extendsfrom config - Base Loading: Requires and instantiates base class
- Main Loading: Requires and instantiates main class
- Merging: Combines tools/prompts/resources intelligently
- Registration: Registers all tools with MCP server
- Ready: Server starts handling requests
Component Structure
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.modelfrom 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:
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:
{
"tools": {
"path": "./srv/lib/my-tools",
"extends": "./srv/lib/mcp-tools-default"
}
}How It Works Internally:
// 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:
Pure Extension (Recommended):
// 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)
Selective Override:
// 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", { ... }); } }Result: Your
search_modelreplaces base's, other base tools inheritedFull Override (Not Recommended):
// 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 (
prompts.extends) - ✅ Resources (
resources.extends)
Multiple Levels of Extension:
{
"tools": {
"path": "./srv/lib/my-production-tools",
"extends": "./srv/lib/my-base-tools"
}
}Where my-base-tools could also extend another base:
// my-base-tools.js
class MyBaseTools extends MCPToolsDefault { ... }Extending the Server
Recommended Approach: Combine Default + Custom Tools
The best practice is to use the default tools for generic CAP operations and add your own domain-specific tools:
{
"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
Debugging Extension Loading
Check Health Endpoint:
curl http://localhost:4004/mcp/healthResponse shows loaded components:
{
"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: 15Reload Components at Runtime:
curl -X POST http://localhost:4004/mcp/reload \
-H "Authorization: Basic <your-api-key>"This reloads all components without restarting the server - useful during development!
Use Cases
1. Model Exploration for AI Assistants
Use default tools to help AI understand your data model:
// 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:
// 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:
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);
}
});
}
}Using Custom Implementations
You can replace the demo components with your own implementations by changing the component paths in your configuration:
{
"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
Adding Custom Tools
Extend the MCPTools class to add your own tools:
// 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 `Custom result: ${args.param}`;
}
});
}
}Adding Prompts and Resources
Similar patterns can be used to extend prompts and resources functionality.
Connecting to MCP Clients
FAQ & Troubleshooting
Q: Why aren't my custom tools showing up?
A: Check these points:
- Verify your class exports correctly:
module.exports = MyTools; - Check constructor initializes the
toolsMap:this.tools = new Map(); - Implement required methods:
getToolDefinitions(),executeToolHandler(), etc. - Check console output during startup for loading errors
- Visit
/mcp/healthto see what tools are actually loaded
Q: Can I use inheritance AND the extends configuration?
A: Yes! Both work together:
// my-tools.js - uses JavaScript inheritance
class MyTools extends MCPToolsDefault {
_initializeTools() {
super._initializeTools(); // Get base tools
this.tools.set("my_tool", { ... }); // Add yours
}
}// package.json - uses configuration extension
{
"tools": {
"path": "./srv/lib/my-tools",
"extends": "./srv/lib/some-other-base"
}
}Result: my-tools 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:
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 → YourClassConfigure: YourClass 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:
curl -X POST http://localhost:4004/mcp/reload \
-H "Authorization: Basic <your-key>"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_modelGitHub Copilot Integration
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:
{
"servers": {
"catalog": {
"type": "http",
"url": "https://your-app-domain.cfapps.region.hana.ondemand.com/mcp",
"headers": {
"Authorization": "Basic <base64-encoded-api-key>"
}
}
}
}Generating the Authorization Header
The authorization header uses Basic authentication with your API key:
# Encode your API key (replace with your actual key)
echo -n "apiKey:YOUR_SECRET_API_KEY" | base64Use the output as the Authorization header value: Basic <base64-output>
Testing the Connection
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 €1899License
This project is licensed under the MIT License. See the LICENSE file for more details.
Technical Details
Quick Reference: Extension Configuration
// 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"
}
}Extension Behavior Matrix
| Scenario | Base Has | Main Has | Result | Logged As |
|----------|----------|----------|--------|-----------|
| Inherit | tool_a | - | tool_a from base | 🔗 Inheriting |
| Override | tool_a | tool_a | tool_a from main | 🔄 Overriding |
| Add New | - | tool_b | tool_b from main | ✨ (no log) |
| Combine | tool_a, tool_b | tool_c | All three | Mix of above |
Default Tools Implementation
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:
cds.resolve()- Find all CDS filescds.load()- Parse and load with docs/locationscds.compile.for.nodejs()- Include drafts and effective typescds.compile.to.serviceinfo()- 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()) - Full support for:
$filter,$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
Comparison: Default vs. Example Tools
| Feature | mcp-tools-default.js | mcp-tools-example.js |
|---------|------------------------|------------------------|
| Purpose | Generic CAP operations | Domain-specific demo |
| Data Source | CDS Model (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 |
Performance Considerations
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
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Keywords
- CAP
- CDS
- MCP
- Model Context Protocol
- AI Assistant
- Tools
- Prompts
- Resources
Acknowledgments
Thanks to the SAP CAP community and the MCP specification contributors for their support and contributions.
