From 4a717714fffed639ea9121026a294ff1214d9163 Mon Sep 17 00:00:00 2001 From: "Dr. David A. Kunz" Date: Tue, 29 Jul 2025 10:19:06 +0200 Subject: [PATCH 1/2] chore: only expose one tool --- lib/setModel.js | 4 ++++ lib/tools.js | 42 +++++++++++++----------------------------- tests/tools.test.js | 18 ++++++++++++------ 3 files changed, 29 insertions(+), 35 deletions(-) diff --git a/lib/setModel.js b/lib/setModel.js index ab7b274..e2f51a4 100644 --- a/lib/setModel.js +++ b/lib/setModel.js @@ -40,6 +40,10 @@ async function compileModel(path) { Object.assign(def, info) } + for (const name in compiled.definitions) { + Object.defineProperty(compiled.definitions[name], 'name', { value: name, enumerable: true }) + } + const _entities_in = service => { const exposed = [], { entities } = service diff --git a/lib/tools.js b/lib/tools.js index ccd37d2..afcc25b 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -5,22 +5,22 @@ import { z } from 'zod' import setModel from './setModel.js' import fuzzyTopN from './fuzzyTopN.js' -const PROJECT_PATH = { - projectPath: z.string().describe('Root path of the project') -} - const tools = { search_cds_definitions: { title: 'Search for CDS definitions', description: 'Get details of CDS definitions, returns Core Schema Notation (CSN). Use this if you want to see elements, parameters, file locations, URL paths, etc., helpful when constructing queries or OData URLs or when modifying CDS models.', inputSchema: { - ...PROJECT_PATH, - name: z.string().optional().describe('Name of the definition (fuzzy search, no regex or special characters)'), - kind: z.string().optional().describe('Kind of the definition (service, entity, action, ...)'), - topN: z.number().default(1).describe('Number of results') + projectPath: z.string().describe('Root path of the project'), + name: z + .string() + .optional() + .describe('Name of the definition (fuzzy search (Levenshtein distance), no regex or special characters)'), + kind: z.string().optional().describe('Filter for kind of the definition (service, entity, action, ...)'), + topN: z.number().default(1).describe('Number of results'), + namesOnly: z.boolean().optional().describe('If true, only return the names of the definitions') }, - handler: async ({ projectPath, name, kind, topN }) => { + handler: async ({ projectPath, name, kind, topN, namesOnly }) => { await setModel(projectPath) const defNames = kind ? Object.entries(cds.model.definitions) @@ -29,26 +29,10 @@ const tools = { .map(([k]) => k) : Object.keys(cds.model.definitions) const scores = name ? fuzzyTopN(name, defNames, topN) : fuzzyTopN('', defNames, topN) - return scores.map(s => Object.assign({ name: s.item }, cds.model.definitions[s.item])) - } - }, - list_all_cds_definition_names: { - title: 'List all CDS definitions names', - description: - 'Get an overview of available CDS definitions, for details use `search_cds_definitions`. Helpful for initial exploration, e.g. to get all service names.', - inputSchema: { - ...PROJECT_PATH, - kind: z.string().optional().describe('Kind of the definition (service, entity, action, ...)') - }, - handler: async ({ projectPath, kind }) => { - await setModel(projectPath) - const defNames = kind - ? Object.entries(cds.model.definitions) - // eslint-disable-next-line no-unused-vars - .filter(([_k, v]) => v.kind === kind) - .map(([k]) => k) - : Object.keys(cds.model.definitions) - return defNames + if (namesOnly) { + return scores.map(s => s.item) + } + return scores.map(s => cds.model.definitions[s.item]) } } } diff --git a/tests/tools.test.js b/tests/tools.test.js index 0836a56..25c5946 100644 --- a/tests/tools.test.js +++ b/tests/tools.test.js @@ -40,21 +40,27 @@ test.describe('tools', () => { assert.equal(books[0].endpoints[0].path, 'odata/v4/admin/Books', 'Should contain endpoint path') }) - test('list_all_cds_definition_names: should list all entities', async () => { - const entities = await tools.list_all_cds_definition_names.handler({ + test('search_cds_definitions: should list all entities (namesOnly)', async () => { + const entities = await tools.search_cds_definitions.handler({ projectPath: sampleProjectPath, - kind: 'entity' + kind: 'entity', + topN: 100, + namesOnly: true }) assert(Array.isArray(entities), 'Entities should be an array') assert(entities.length > 0, 'Should find at least one entity') + assert(typeof entities[0] === 'string', 'Should return only names') }) - test('list_all_cds_definition_names: should list all services', async () => { - const services = await tools.list_all_cds_definition_names.handler({ + test('search_cds_definitions: should list all services (namesOnly)', async () => { + const services = await tools.search_cds_definitions.handler({ projectPath: sampleProjectPath, - kind: 'service' + kind: 'service', + topN: 100, + namesOnly: true }) assert(Array.isArray(services), 'Services should be an array') assert(services.length > 0, 'Should find at least one service') + assert(typeof services[0] === 'string', 'Should return only names') }) }) From 44884270f6ef965eadc10052a3ef3802a0e30c7a Mon Sep 17 00:00:00 2001 From: "Dr. David A. Kunz" Date: Tue, 29 Jul 2025 10:26:16 +0200 Subject: [PATCH 2/2] more tests --- tests/sample/db/schema.cds | 1 + tests/tools.test.js | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/tests/sample/db/schema.cds b/tests/sample/db/schema.cds index ff7017c..a6bf0fd 100644 --- a/tests/sample/db/schema.cds +++ b/tests/sample/db/schema.cds @@ -1,6 +1,7 @@ using { Currency, managed, sap } from '@sap/cds/common'; namespace sap.capire.bookshop; +@odata.draft.enabled entity Books : managed { key ID : Integer; @mandatory title : localized String(111); diff --git a/tests/tools.test.js b/tests/tools.test.js index 25c5946..8353860 100644 --- a/tests/tools.test.js +++ b/tests/tools.test.js @@ -38,6 +38,15 @@ test.describe('tools', () => { assert(Array.isArray(books[0].endpoints), 'Should contain endpoints') assert.equal(books[0].endpoints[0].kind, 'odata', 'Should contain odata endpoint kind') assert.equal(books[0].endpoints[0].path, 'odata/v4/admin/Books', 'Should contain endpoint path') + + // Check that keys are present and correct + assert(books[0].elements.ID, 'Books entity should have key ID') + assert(books[0].elements.ID.key === true, 'ID should be marked as key') + // Check draft fields + assert(books[0].elements.IsActiveEntity, 'Draft-enabled entity should have IsActiveEntity') + assert(books[0].elements.IsActiveEntity.key === true, 'IsActiveEntity should be marked as key') + assert(books[0].elements.HasActiveEntity, 'Draft-enabled entity should have HasActiveEntity') + assert(books[0].elements.HasDraftEntity, 'Draft-enabled entity should have HasDraftEntity') }) test('search_cds_definitions: should list all entities (namesOnly)', async () => {