From eafe628ec61fd3f87dcff6c135fe9380f8af6e3f Mon Sep 17 00:00:00 2001 From: RGA29 Date: Mon, 4 Aug 2025 09:13:01 -0300 Subject: [PATCH 1/2] [CPL-20880] Added error guidance for get-dataflow and list-dataflow --- src/tools/get-dataflow/handler.test.ts | 21 ++++++++++++++++++++- src/tools/get-dataflow/handler.ts | 8 +++++--- src/tools/list-dataflows/handler.test.ts | 23 ++++++++++++++++++++++- src/tools/list-dataflows/handler.ts | 8 +++++--- src/util/tool-response.ts | 20 ++++++++++++++++++++ 5 files changed, 72 insertions(+), 8 deletions(-) diff --git a/src/tools/get-dataflow/handler.test.ts b/src/tools/get-dataflow/handler.test.ts index 214647c..096d3b4 100644 --- a/src/tools/get-dataflow/handler.test.ts +++ b/src/tools/get-dataflow/handler.test.ts @@ -23,6 +23,10 @@ const mockGetDataflowError = createMockResponse( async () => new Response('Error when getting dataflow', { status: 500 }) ) +const mockGetDataflowErrorWithDetails = createMockResponse( + async () => new Response(JSON.stringify({ error: { message: 'Test error message' } }), { status: 500 }) +) + const mockFetch = vi.spyOn(globalThis, 'fetch') describe('get-dataflow', () => { @@ -63,7 +67,22 @@ describe('get-dataflow', () => { isError: true, content: [{ type: 'text', - text: 'Failed to get data flow gsheet_dataflow. Response status: 500', + text: 'Failed to get data flow gsheet_dataflow. Response status: 500.', + }] + }) + }) + + it('includes error details when present', async () => { + mockFetch + .mockImplementationOnce(mockGetDataflowErrorWithDetails) + + const toolResult = await handler({ dataflowId: 'gsheet_dataflow' }) + + expect(toolResult).toEqual({ + isError: true, + content: [{ + type: 'text', + text: 'Failed to get data flow gsheet_dataflow. Response status: 500. Error details: Test error message', }] }) }) diff --git a/src/tools/get-dataflow/handler.ts b/src/tools/get-dataflow/handler.ts index 0a4525a..7f649ea 100644 --- a/src/tools/get-dataflow/handler.ts +++ b/src/tools/get-dataflow/handler.ts @@ -3,7 +3,7 @@ import { fromError } from 'zod-validation-error' import { zodInputSchema } from './schema.js' -import { textResponse } from '../../util/tool-response.js' +import { textResponse, buildErrorMessage } from '../../util/tool-response.js' import { COUPLER_ACCESS_TOKEN } from '../../env.js' import { CouplerioClient } from '../../lib/couplerio-client/index.js' import { logger } from '../../logger/index.js' @@ -33,10 +33,12 @@ export const handler = async (params?: Record): Promise new Response('Error listing dataflows', { status: 500 }) ) +const mockGetDataflowErrorWithDetails = createMockResponse( + async () => new Response( + JSON.stringify({ error: { message: 'Test error message' } }), { status: 500 } + ) +) + const mockFetch = vi.spyOn(globalThis, 'fetch') describe('listDataflows', () => { @@ -63,7 +69,22 @@ describe('listDataflows', () => { isError: true, content: [{ type: 'text', - text: 'Failed to list data flows. Response status: 500', + text: 'Failed to list data flows. Response status: 500.', + }] + }) + }) + + it('includes error details when present', async () => { + mockFetch + .mockImplementationOnce(mockGetDataflowErrorWithDetails) + + const toolResult = await handler() + + expect(toolResult).toEqual({ + isError: true, + content: [{ + type: 'text', + text: 'Failed to list data flows. Response status: 500. Error details: Test error message', }] }) }) diff --git a/src/tools/list-dataflows/handler.ts b/src/tools/list-dataflows/handler.ts index 8f59359..fd0e3b6 100644 --- a/src/tools/list-dataflows/handler.ts +++ b/src/tools/list-dataflows/handler.ts @@ -1,6 +1,6 @@ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js' -import { textResponse } from '../../util/tool-response.js' +import { textResponse, buildErrorMessage } from '../../util/tool-response.js' import { COUPLER_ACCESS_TOKEN } from '../../env.js' import { CouplerioClient } from '../../lib/couplerio-client/index.js' import { logger } from '../../logger/index.js' @@ -12,10 +12,12 @@ export const handler = async (): Promise => { const response = await coupler.request(`/dataflows?${query}{?type}`) if (!response.ok) { - logger.error(`Failed to list dataflows. Response status: ${response.status}`) + const errorText = await buildErrorMessage({ response, customText: 'Failed to list data flows.'}) + + logger.error(errorText) return textResponse({ isError: true, - text: `Failed to list data flows. Response status: ${response.status}` + text: errorText }) } diff --git a/src/util/tool-response.ts b/src/util/tool-response.ts index a459dd0..3a6c02a 100644 --- a/src/util/tool-response.ts +++ b/src/util/tool-response.ts @@ -17,3 +17,23 @@ export const textResponse = ({ text, isError = false, structuredContent }: { tex return callToolResult } + +export const buildErrorMessage = async ({ + response, + customText = 'An unexpected error occurred.', +}: { + response: Response, + customText: string, +}): Promise => { + let errorDetails = '' + + try { + const { error } = (await response.json()) as { error?: { message?: string } } + errorDetails = error?.message ?? '' + } catch { + // Does not update for JSON parse errors + } + + return `${customText} Response status: ${response.status}.` + + (errorDetails ? ` Error details: ${errorDetails}` : '') +} \ No newline at end of file From 2adcd866a14e14ed99ccfa11759c7cbc1f7cbeb2 Mon Sep 17 00:00:00 2001 From: RGA29 Date: Wed, 6 Aug 2025 09:58:38 -0300 Subject: [PATCH 2/2] [CPL-20880] Updated tool-response to remove paranthesis and log JSON parse error --- src/util/tool-response.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/util/tool-response.ts b/src/util/tool-response.ts index 3a6c02a..2cdad72 100644 --- a/src/util/tool-response.ts +++ b/src/util/tool-response.ts @@ -1,4 +1,5 @@ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js' +import { logger } from '../logger/index.js' export const textResponse = ({ text, isError = false, structuredContent }: { text: string, isError?: boolean, structuredContent?: Record }) => { const callToolResult: CallToolResult = { @@ -28,10 +29,11 @@ export const buildErrorMessage = async ({ let errorDetails = '' try { - const { error } = (await response.json()) as { error?: { message?: string } } + const { error } = await response.json() as { error?: { message?: string } } errorDetails = error?.message ?? '' - } catch { + } catch (err){ // Does not update for JSON parse errors + logger.error('Failed to parse JSON response', err) } return `${customText} Response status: ${response.status}.` +