Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ All configuration lives under `cds.mcp` in your `package.json`:

| Flag | Default | Description |
| ----------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `per_action_tool` | `false` | Expose each action/function as its own dedicated tool instead of the generic `call_action` tool. |
| `per_action_tool` | `false` | Expose each action/function as its own dedicated tool instead of the generic `call` tool. |
| `toon_format` | `true` | Return query results in [TOON](https://www.npmjs.com/package/@toon-format/toon) format. Set to `false` to use JSON instead. |
| `prefix` | `false` | Prefix tool names with the slugified service name to avoid collisions when a MCP client connects to multiple MCP servers (e.g. `catalog_query`, `admin_describe`). |
| `format` | `"cqn"` | Experimental: Change the `query` input format. `"cqn"` (default) uses structured CQN input. `"sql"` switches the `query` tool to accept a plain SQL `SELECT` statement |
| `format` | `"cqn"` | Experimental: Change the `query` input format. `"cqn"` (default) uses structured CQN input. `"cql"` switches the `query` tool to accept a plain SQL `SELECT` statement |

For all other configuration options, refer to the official [documentation](https://cap.cloud.sap/docs/guides/protocols/mcp).

Expand Down
2 changes: 1 addition & 1 deletion lib/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ function generateToolsForService(def, model) {
})
}
} else {
// Register generic call_action tool
// Register generic call tool
const actionDef = createCallActionToolDefinition(actionNames, def.name, prefix)
tools.push({
name: actionDef.name,
Expand Down
24 changes: 12 additions & 12 deletions lib/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@
const LOCALIZED_ELEMENTS = ['localized', 'texts']

const DEFAULT_INSTRUCTIONS =
"Use the 'describe' tool to explore the data model and available actions/functions. Then use 'query' to read data or 'call_action' to invoke actions or functions."
"Use the 'describe' tool to explore the data model and available actions/functions. Then use 'query' to read data or 'call' to invoke actions or functions."

function getInstructions(def, locale, prefix) {
locale = locale || cds.context?.locale || 'en'
const custom = resolveI18n(def['@mcp.instructions'], locale)
if (custom) return custom
if (!prefix) return DEFAULT_INSTRUCTIONS
return `Use the '${prefix}_describe' tool to explore the data model and available actions/functions. Then use '${prefix}_query' to read data or '${prefix}_call_action' to invoke actions or functions.`
return `Use the '${prefix}_describe' tool to explore the data model and available actions/functions. Then use '${prefix}_query' to read data or '${prefix}_call' to invoke actions or functions.`
}

// Create MCP error response
Expand Down Expand Up @@ -126,10 +126,10 @@

function createGenericReadToolDefinition(entityNames, serviceName, prefix) {
const name = prefix ? `${prefix}_query` : 'query'
if (cds.env.mcp?.format === 'sql') {
if (cds.env.mcp?.format === 'cql') {
return {
name,
description: `Execute a SQL SELECT query against ${serviceName} service. Use describe to discover available entities and their fields (returned as CDS definitions). Only SELECT statements are allowed; LIMIT is auto-clamped to the service max. Only standard CAP/OData functions and aggregates are permitted (count, sum, avg, min, max, lower, upper, substring, year, month, day, etc.). Database system functions like CURRENT_USER, SESSION_USER, SYSUUID, and pseudo-columns like CURRENT_SCHEMA are rejected.`,
description: `Execute a CAP CQL (a superset of SQL) SELECT query against ${serviceName} service. Use describe to discover available entities and their fields (returned as CDS definitions). Only SELECT statements are allowed; LIMIT is auto-clamped to the service max. All generic CAP functions and aggregates are permitted (count, sum, avg, min, max, lower, upper, substring, year, month, day, etc.). Database system functions like CURRENT_USER, SESSION_USER, SYSUUID, and pseudo-columns like CURRENT_SCHEMA are rejected. Use path expressions to navigate along associtations instead of joins.`,
inputSchema: z.object({
sql: z
.string()
Expand Down Expand Up @@ -191,7 +191,7 @@
}

function createCallActionToolDefinition(actionNames, serviceName, prefix) {
const name = prefix ? `${prefix}_call_action` : 'call_action'
const name = prefix ? `${prefix}_call` : 'call'
return {
name,
description: `Call an unbound action or function in ${serviceName} service. Use describe to discover available actions and their parameters.`,
Expand Down Expand Up @@ -252,7 +252,7 @@
log.debug('Registered tool', { tool: def.name, service: srv.name })
}

// Register the call_action tool for invoking unbound actions/functions
// Register the call tool for invoking unbound actions/functions
function registerCallActionTool(server, srv, actions, prefix, { log = LOG } = {}) {
const actionNames = Object.keys(actions)
if (actionNames.length === 0) return // No actions to register
Expand Down Expand Up @@ -406,14 +406,14 @@
}

async function executeGenericReadTool(srv, entities, args, { log = LOG } = {}) {
if (cds.env.mcp?.format === 'sql') {
if (cds.env.mcp?.format === 'cql') {
return _executeGenericReadSql(srv, entities, args, { log })
}
return _executeGenericReadCqn(srv, entities, args, { log })
}

// Default SQL length cap. Override via cds.env.mcp.sql.maxLength.
const DEFAULT_SQL_MAX_LENGTH = 10_000
// Default SQL length cap. Override via cds.env.mcp.cql.maxLength.
const DEFAULT_CQL_MAX_LENGTH = 10_000

// Run a pre-built CQN: validate + LIMIT + srv.run.
// Returns { ok:true, data, count, one } or { ok:false, code, reason, entity, err }.
Expand Down Expand Up @@ -500,7 +500,7 @@
async function _executeGenericReadSql(srv, entities, args, { log = LOG } = {}) {
const rawSql = args.sql ?? ''

const maxLength = cds.env.mcp?.sql?.maxLength ?? DEFAULT_SQL_MAX_LENGTH
const maxLength = cds.env.mcp?.cql?.maxLength ?? DEFAULT_CQL_MAX_LENGTH
if (rawSql.length > maxLength) {
return errorResponse(
`Error: SQL exceeds max length of ${maxLength} characters (received ${rawSql.length}).`
Expand Down Expand Up @@ -628,8 +628,8 @@
}
}

async function executeDescribe(srv, entities, actions, args, { log = LOG } = {}) {

Check warning on line 631 in lib/tools.js

View workflow job for this annotation

GitHub Actions / lint

'log' is assigned a value but never used
if (cds.env.mcp?.format === 'sql') {
if (cds.env.mcp?.format === 'cql') {
return _executeDescribeCdl(srv, entities, actions, args, { log: LOG })
}
return _executeDescribeCsn(srv, entities, actions, args, { log: LOG })
Expand Down Expand Up @@ -800,7 +800,7 @@
}

// CDL format (sql mode): return CDS definitions via cds.compile.to.cdl
async function _executeDescribeCdl(srv, entities, actions, args, { log = LOG } = {}) {

Check warning on line 803 in lib/tools.js

View workflow job for this annotation

GitHub Actions / lint

'log' is assigned a value but never used
const hasEntity = args.entities?.length > 0
const hasAction = args.actions?.length > 0
const includeEntities = hasEntity || !hasAction
Expand Down Expand Up @@ -879,7 +879,7 @@
async function executeCallActionTool(srv, actions, args, { log = LOG } = {}) {
const { action: actionName, parameters = {} } = args
log(
'call_action',
'call',
fmt({ service: srv.name, action: actionName, parameters: buildQueryArgs(parameters) })
)

Expand Down
13 changes: 0 additions & 13 deletions tests/bookshop/app/admin-books/fiori-service.cds
Original file line number Diff line number Diff line change
Expand Up @@ -87,19 +87,6 @@ annotate AdminService.Books.texts with @(UI: {
]
});

annotate AdminService.Books.texts with {
ID @UI.Hidden;
ID_texts @UI.Hidden;
};

// Add Value Help for Locales
annotate AdminService.Books.texts {
locale @(
ValueList.entity: 'Languages',
Common.ValueListWithFixedValues, //show as drop down, not a dialog
)
};

// In addition we need to expose Languages through AdminService as a target for ValueList
using {sap} from '@sap/cds/common';

Expand Down
2 changes: 1 addition & 1 deletion tests/bookshop/srv/cat-service.cds
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,4 @@ annotate CatalogService.Books with {
};

annotate CatalogService with @mcp @odata;
annotate CatalogService with @mcp.instructions: 'Use describe to explore available books, genres, and actions. Use query to search the catalog. Use call_action to place orders or perform calculations.';
annotate CatalogService with @mcp.instructions: 'Use describe to explore available books, genres, and actions. Use query to search the catalog. Use call to place orders or perform calculations.';
4 changes: 2 additions & 2 deletions tests/integration/__snapshots__/catalog-service-card.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"version": "1.0.0",
"supportedProtocolVersions": ["2025-11-25"],
"description": "Catalog service for browsing books.\nProvides read access to the book catalog including genres and au",
"instructions": "Use describe to explore available books, genres, and actions. Use query to search the catalog. Use call_action to place orders or perform calculations.",
"instructions": "Use describe to explore available books, genres, and actions. Use query to search the catalog. Use call to place orders or perform calculations.",
"remotes": [
{
"type": "streamable-http",
Expand Down Expand Up @@ -612,7 +612,7 @@
}
},
{
"name": "call_action",
"name": "call",
"description": "Call an unbound action or function in CatalogService service. Use describe to discover available actions and their parameters.",
"inputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
Expand Down
Loading
Loading