From f65af1f4429eeea818a3a8fafbf2f8e2015f6b01 Mon Sep 17 00:00:00 2001 From: Stefan Rudi Date: Thu, 27 Aug 2026 16:33:44 +0200 Subject: [PATCH 1/4] fix: consider prefix in the query tool description for describe --- CHANGELOG.md | 6 + lib/tools/query.js | 5 +- .../__snapshots__/catalog-service-card.json | 2 +- tests/integration/mcp-test-client.js | 104 +++++++++--------- tests/integration/prefix.test.js | 7 ++ 5 files changed, 66 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ecf132..5ee83d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ - The format is based on [Keep a Changelog](https://keepachangelog.com/). - This project adheres to [Semantic Versioning](https://semver.org/). +## Version 1.4.4 - tbd + +### Fixed + +- Consider the prefix in description of the `query` tool + ## Version 1.4.3 - 2026-08-14 ### Fixed diff --git a/lib/tools/query.js b/lib/tools/query.js index 0491abf..a3dd6ec 100644 --- a/lib/tools/query.js +++ b/lib/tools/query.js @@ -52,7 +52,7 @@ function createGenericReadToolDefinition(entityNames, serviceName, prefix = '') if (cds.env.mcp?.format === 'cqn') { return { name, - description: `Query any entity in ${serviceName} service. Use describe to discover available entities and their fields.`, + description: `Query any entity in ${serviceName} service. Use ${prefix}describe to discover available entities and their fields.`, inputSchema: createReadInputSchema({ entityNames }), annotations: { readOnlyHint: true, @@ -65,8 +65,7 @@ function createGenericReadToolDefinition(entityNames, serviceName, prefix = '') return { name, description: - `Query any entity in ${serviceName} service.` + - "Ensure to first use the `describe` tool to discover an entity's available fields.", + `Query any entity in ${serviceName} service. Ensure to first use the ${prefix}describe tool to discover an entity's available fields.`, inputSchema: z.object({ cql: z .string() diff --git a/tests/integration/__snapshots__/catalog-service-card.json b/tests/integration/__snapshots__/catalog-service-card.json index fde2fb5..d6b92c9 100644 --- a/tests/integration/__snapshots__/catalog-service-card.json +++ b/tests/integration/__snapshots__/catalog-service-card.json @@ -18,7 +18,7 @@ "tools": [ { "name": "query", - "description": "Query any entity in CatalogService service.Ensure to first use the `describe` tool to discover an entity's available fields.", + "description": "Query any entity in CatalogService service. Ensure to first use the describe tool to discover an entity's available fields.", "inputSchema": { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", diff --git a/tests/integration/mcp-test-client.js b/tests/integration/mcp-test-client.js index df50759..d72636d 100644 --- a/tests/integration/mcp-test-client.js +++ b/tests/integration/mcp-test-client.js @@ -1,3 +1,5 @@ +const { decode } = require('@toon-format/toon'); + async function parseResponseStream(data) { const str = typeof data === 'string' ? data : await new Response(data).text() const line = str.split('\n').find((l) => l.startsWith('data: ')) @@ -5,68 +7,62 @@ async function parseResponseStream(data) { return JSON.parse(line.slice(6)) } -function parseContent(text) { - // require works via Vitest mock in tests/setup.js - const toon = require('@toon-format/toon') - return toon.decode(text) -} - module.exports = (test) => - (endpoint = '/mcp/catalog', auth = null, locale = null) => { - let requestId = 0 + (endpoint = '/mcp/catalog', auth = null, locale = null) => { + let requestId = 0 - const getHeaders = () => { - const headers = { - 'Content-Type': 'application/json', - Accept: 'application/json, text/event-stream' - } - if (auth) { - headers['Authorization'] = `Basic ${Buffer.from(auth).toString('base64')}` - } - if (locale) { - headers['Accept-Language'] = locale + const getHeaders = () => { + const headers = { + 'Content-Type': 'application/json', + Accept: 'application/json, text/event-stream' + } + if (auth) { + headers['Authorization'] = `Basic ${Buffer.from(auth).toString('base64')}` + } + if (locale) { + headers['Accept-Language'] = locale + } + return headers } - return headers - } - const mcp = async (method, params = {}) => { - try { - const response = await test.POST( - endpoint, - { jsonrpc: '2.0', id: ++requestId, method, params }, - { headers: getHeaders() } - ) - return parseResponseStream(response.data) - } catch (err) { - // Handle HTTP errors (401, 403) from authorization failures - if (err.response?.data) return err.response.data - return { error: { message: err.message } } + const mcp = async (method, params = {}) => { + try { + const response = await test.POST( + endpoint, + { jsonrpc: '2.0', id: ++requestId, method, params }, + { headers: getHeaders() } + ) + return parseResponseStream(response.data) + } catch (err) { + // Handle HTTP errors (401, 403) from authorization failures + if (err.response?.data) return err.response.data + return { error: { message: err.message } } + } } - } - const initialize = () => - mcp('initialize', { - protocolVersion: '2025-11-25', - capabilities: {}, - clientInfo: { name: 'test-client', version: '1.0.0' } - }) + const initialize = () => + mcp('initialize', { + protocolVersion: '2025-11-25', + capabilities: {}, + clientInfo: { name: 'test-client', version: '1.0.0' } + }) - const callTool = async (name, args = {}) => { - try { - const res = await mcp('tools/call', { name, arguments: args }) - if (res.error) { - return { ...res, content: null, error: res.error.message } + const callTool = async (name, args = {}) => { + try { + const res = await mcp('tools/call', { name, arguments: args }) + if (res.error) { + return { ...res, content: null, error: res.error.message } + } + return { + ...res, + content: res.result.isError ? null : decode(res.result.content[0].text), + error: res.result.isError ? res.result.content[0].text : null + } + } catch (err) { + return { content: null, error: `callTool(${name}) failed: ${err.message}` } } - return { - ...res, - content: res.result.isError ? null : parseContent(res.result.content[0].text), - error: res.result.isError ? res.result.content[0].text : null - } - } catch (err) { - return { content: null, error: `callTool(${name}) failed: ${err.message}` } } - } - return { mcp, callTool, initialize } - } + return { mcp, callTool, initialize } + } diff --git a/tests/integration/prefix.test.js b/tests/integration/prefix.test.js index 78c4369..9138754 100644 --- a/tests/integration/prefix.test.js +++ b/tests/integration/prefix.test.js @@ -44,6 +44,13 @@ describe('Tool Name Prefix (global prefix: true)', () => { expect(content.data.length).to.be.greaterThan(0) }) + it('query tool description considers prefix for describe', async () => { + const response = await mcp('tools/list') + const queryTool = response.result.tools.find((t) => t.name === 'CatalogService-query') + expect(queryTool).to.exist + expect(queryTool.description).to.include('CatalogService-describe') + }) + it('prefixed call tool works', async () => { const { content, error } = await callTool('CatalogService-call', { action: 'sum', From 99913c1da6d65d203d9f111b41490bff3cc52f3e Mon Sep 17 00:00:00 2001 From: Stefan Rudi Date: Thu, 27 Aug 2026 16:34:01 +0200 Subject: [PATCH 2/4] . --- lib/tools/query.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/tools/query.js b/lib/tools/query.js index a3dd6ec..7110086 100644 --- a/lib/tools/query.js +++ b/lib/tools/query.js @@ -64,8 +64,7 @@ function createGenericReadToolDefinition(entityNames, serviceName, prefix = '') return { name, - description: - `Query any entity in ${serviceName} service. Ensure to first use the ${prefix}describe tool to discover an entity's available fields.`, + description: `Query any entity in ${serviceName} service. Ensure to first use the ${prefix}describe tool to discover an entity's available fields.`, inputSchema: z.object({ cql: z .string() From 4b745ff9cf1984610714c9338dc057c789441c6b Mon Sep 17 00:00:00 2001 From: Stefan Rudi Date: Thu, 27 Aug 2026 16:38:10 +0200 Subject: [PATCH 3/4] revert changes in mcp-test-client.js --- tests/integration/mcp-test-client.js | 104 ++++++++++++++------------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/tests/integration/mcp-test-client.js b/tests/integration/mcp-test-client.js index d72636d..eb6f10c 100644 --- a/tests/integration/mcp-test-client.js +++ b/tests/integration/mcp-test-client.js @@ -1,5 +1,3 @@ -const { decode } = require('@toon-format/toon'); - async function parseResponseStream(data) { const str = typeof data === 'string' ? data : await new Response(data).text() const line = str.split('\n').find((l) => l.startsWith('data: ')) @@ -7,62 +5,68 @@ async function parseResponseStream(data) { return JSON.parse(line.slice(6)) } +function parseContent(text) { + // require works via Vitest mock in tests/setup.js + const toon = require('@toon-format/toon') + return toon.decode(text) +} + module.exports = (test) => - (endpoint = '/mcp/catalog', auth = null, locale = null) => { - let requestId = 0 + (endpoint = '/mcp/catalog', auth = null, locale = null) => { + let requestId = 0 - const getHeaders = () => { - const headers = { - 'Content-Type': 'application/json', - Accept: 'application/json, text/event-stream' - } - if (auth) { - headers['Authorization'] = `Basic ${Buffer.from(auth).toString('base64')}` - } - if (locale) { - headers['Accept-Language'] = locale - } - return headers + const getHeaders = () => { + const headers = { + 'Content-Type': 'application/json', + Accept: 'application/json, text/event-stream' + } + if (auth) { + headers['Authorization'] = `Basic ${Buffer.from(auth).toString('base64')}` } + if (locale) { + headers['Accept-Language'] = locale + } + return headers + } - const mcp = async (method, params = {}) => { - try { - const response = await test.POST( - endpoint, - { jsonrpc: '2.0', id: ++requestId, method, params }, - { headers: getHeaders() } - ) - return parseResponseStream(response.data) - } catch (err) { - // Handle HTTP errors (401, 403) from authorization failures - if (err.response?.data) return err.response.data - return { error: { message: err.message } } - } + const mcp = async (method, params = {}) => { + try { + const response = await test.POST( + endpoint, + { jsonrpc: '2.0', id: ++requestId, method, params }, + { headers: getHeaders() } + ) + return parseResponseStream(response.data) + } catch (err) { + // Handle HTTP errors (401, 403) from authorization failures + if (err.response?.data) return err.response.data + return { error: { message: err.message } } } + } - const initialize = () => - mcp('initialize', { - protocolVersion: '2025-11-25', - capabilities: {}, - clientInfo: { name: 'test-client', version: '1.0.0' } - }) + const initialize = () => + mcp('initialize', { + protocolVersion: '2025-11-25', + capabilities: {}, + clientInfo: { name: 'test-client', version: '1.0.0' } + }) - const callTool = async (name, args = {}) => { - try { - const res = await mcp('tools/call', { name, arguments: args }) - if (res.error) { - return { ...res, content: null, error: res.error.message } - } - return { - ...res, - content: res.result.isError ? null : decode(res.result.content[0].text), - error: res.result.isError ? res.result.content[0].text : null - } - } catch (err) { - return { content: null, error: `callTool(${name}) failed: ${err.message}` } + const callTool = async (name, args = {}) => { + try { + const res = await mcp('tools/call', { name, arguments: args }) + if (res.error) { + return { ...res, content: null, error: res.error.message } } + return { + ...res, + content: res.result.isError ? null : parseContent(res.result.content[0].text), + error: res.result.isError ? res.result.content[0].text : null + } + } catch (err) { + return { content: null, error: `callTool(${name}) failed: ${err.message}` } } - - return { mcp, callTool, initialize } } + + return { mcp, callTool, initialize } + } \ No newline at end of file From 7b526c3e1c8707bbc9bb49b6b58f5802887ca21d Mon Sep 17 00:00:00 2001 From: Stefan Rudi Date: Thu, 27 Aug 2026 16:42:08 +0200 Subject: [PATCH 4/4] . --- tests/integration/mcp-test-client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/mcp-test-client.js b/tests/integration/mcp-test-client.js index eb6f10c..df50759 100644 --- a/tests/integration/mcp-test-client.js +++ b/tests/integration/mcp-test-client.js @@ -69,4 +69,4 @@ module.exports = } return { mcp, callTool, initialize } - } \ No newline at end of file + }