From f17dd1924da174947d97d5811540e2cb929e4674 Mon Sep 17 00:00:00 2001 From: jiangyuer018 <2877716584@qq.com> Date: Tue, 3 Nov 2026 23:29:19 +0800 Subject: [PATCH 1/2] fix(hdx-eval): reject invalid MCP evidence content --- packages/hdx-eval/src/__tests__/api.test.ts | 37 +++++++++++++++++++++ packages/hdx-eval/src/hyperdx/api.ts | 10 ++++-- 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 packages/hdx-eval/src/__tests__/api.test.ts diff --git a/packages/hdx-eval/src/__tests__/api.test.ts b/packages/hdx-eval/src/__tests__/api.test.ts new file mode 100644 index 0000000000..54ecba0a94 --- /dev/null +++ b/packages/hdx-eval/src/__tests__/api.test.ts @@ -0,0 +1,37 @@ +import { HyperdxApiClient } from '@/hyperdx/api'; + +const originalFetch = global.fetch; + +afterEach(() => { + global.fetch = originalFetch; +}); + +describe('HyperdxApiClient', () => { + it('reports invalid MCP content as a failed tile query', async () => { + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + text: async () => + JSON.stringify({ + jsonrpc: '2.0', + result: { + content: [{ type: 'text', text: 'not valid JSON' }], + }, + }), + }) as unknown as typeof fetch; + + const result = await new HyperdxApiClient( + 'http://localhost', + ).queryTileWithEvidence({ + accessKey: 'test-key', + dashboardId: 'dashboard-id', + tileId: 'tile-id', + startTime: '2026-01-01T00:00:00.000Z', + endTime: '2026-01-01T01:00:00.000Z', + }); + + expect(result.success).toBe(false); + expect(result.hasData).toBe(false); + expect(result.error).toMatch(/^Invalid MCP content:/); + }); +}); diff --git a/packages/hdx-eval/src/hyperdx/api.ts b/packages/hdx-eval/src/hyperdx/api.ts index b83d148bfc..aa2a0a3a06 100644 --- a/packages/hdx-eval/src/hyperdx/api.ts +++ b/packages/hdx-eval/src/hyperdx/api.ts @@ -368,8 +368,14 @@ export class HyperdxApiClient { groupCount, sampleRows: rows.slice(0, 5), }; - } catch { - return { success: true, hasData: content.text.length > 0 }; + } catch (error) { + return { + success: false, + hasData: false, + error: `Invalid MCP content: ${ + error instanceof Error ? error.message : String(error) + }`, + }; } } } From 9bab068049d09abf342b89164039bcc1c2b8c619 Mon Sep 17 00:00:00 2001 From: jiangyuer018 <2877716584@qq.com> Date: Tue, 3 Nov 2026 23:46:34 +0800 Subject: [PATCH 2/2] test(hdx-eval): preserve empty MCP evidence results --- packages/hdx-eval/src/__tests__/api.test.ts | 90 ++++++++++++++++++--- packages/hdx-eval/src/hyperdx/api.ts | 6 +- 2 files changed, 84 insertions(+), 12 deletions(-) diff --git a/packages/hdx-eval/src/__tests__/api.test.ts b/packages/hdx-eval/src/__tests__/api.test.ts index 54ecba0a94..573659f5ad 100644 --- a/packages/hdx-eval/src/__tests__/api.test.ts +++ b/packages/hdx-eval/src/__tests__/api.test.ts @@ -2,23 +2,27 @@ import { HyperdxApiClient } from '@/hyperdx/api'; const originalFetch = global.fetch; +function mockMcpContent(text: string): void { + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + text: async () => + JSON.stringify({ + jsonrpc: '2.0', + result: { + content: [{ type: 'text', text }], + }, + }), + }) as unknown as typeof fetch; +} + afterEach(() => { global.fetch = originalFetch; }); describe('HyperdxApiClient', () => { it('reports invalid MCP content as a failed tile query', async () => { - global.fetch = jest.fn().mockResolvedValue({ - ok: true, - status: 200, - text: async () => - JSON.stringify({ - jsonrpc: '2.0', - result: { - content: [{ type: 'text', text: 'not valid JSON' }], - }, - }), - }) as unknown as typeof fetch; + mockMcpContent('not valid JSON'); const result = await new HyperdxApiClient( 'http://localhost', @@ -34,4 +38,68 @@ describe('HyperdxApiClient', () => { expect(result.hasData).toBe(false); expect(result.error).toMatch(/^Invalid MCP content:/); }); + + it('treats empty MCP content as a successful query with no data', async () => { + mockMcpContent(' \n'); + + const result = await new HyperdxApiClient( + 'http://localhost', + ).queryTileWithEvidence({ + accessKey: 'test-key', + dashboardId: 'dashboard-id', + tileId: 'tile-id', + startTime: '2026-01-01T00:00:00.000Z', + endTime: '2026-01-01T01:00:00.000Z', + }); + + expect(result).toEqual({ success: true, hasData: false }); + }); + + it('extracts rows and groups from valid MCP content', async () => { + mockMcpContent( + JSON.stringify({ + result: [ + { ServiceName: 'api', value: 1 }, + { ServiceName: 'worker', value: 2 }, + ], + }), + ); + + const result = await new HyperdxApiClient( + 'http://localhost', + ).queryTileWithEvidence({ + accessKey: 'test-key', + dashboardId: 'dashboard-id', + tileId: 'tile-id', + startTime: '2026-01-01T00:00:00.000Z', + endTime: '2026-01-01T01:00:00.000Z', + }); + + expect(result).toEqual({ + success: true, + hasData: true, + rowCount: 2, + groupCount: 2, + sampleRows: [ + { ServiceName: 'api', value: 1 }, + { ServiceName: 'worker', value: 2 }, + ], + }); + }); + + it('treats valid MCP content without a result as no data', async () => { + mockMcpContent('{}'); + + const result = await new HyperdxApiClient( + 'http://localhost', + ).queryTileWithEvidence({ + accessKey: 'test-key', + dashboardId: 'dashboard-id', + tileId: 'tile-id', + startTime: '2026-01-01T00:00:00.000Z', + endTime: '2026-01-01T01:00:00.000Z', + }); + + expect(result).toEqual({ success: true, hasData: false }); + }); }); diff --git a/packages/hdx-eval/src/hyperdx/api.ts b/packages/hdx-eval/src/hyperdx/api.ts index aa2a0a3a06..610c8eb831 100644 --- a/packages/hdx-eval/src/hyperdx/api.ts +++ b/packages/hdx-eval/src/hyperdx/api.ts @@ -325,6 +325,10 @@ export class HyperdxApiClient { }; } + if (!content.text.trim()) { + return { success: true, hasData: false }; + } + try { const inner = JSON.parse(content.text); const result = inner.result; @@ -374,7 +378,7 @@ export class HyperdxApiClient { hasData: false, error: `Invalid MCP content: ${ error instanceof Error ? error.message : String(error) - }`, + }`.slice(0, 300), }; } }