mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-28 08:49:38 +02:00
fix: classify mcp query failures (#302)
This commit is contained in:
parent
8a50601582
commit
7e29543398
8 changed files with 102 additions and 8 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { KtxExpectedError, KtxQueryError } from '../../../src/errors.js';
|
||||
import {
|
||||
assertReadOnlySql,
|
||||
limitSqlForExecution,
|
||||
|
|
@ -20,6 +21,20 @@ describe('assertReadOnlySql', () => {
|
|||
);
|
||||
});
|
||||
|
||||
// A guard refusing the agent's SQL is an expected outcome; classifying it as
|
||||
// KtxQueryError keeps reportException from filing it as a ktx fault.
|
||||
it('rejects with an expected KtxQueryError, not a bare Error', () => {
|
||||
expect(() => assertReadOnlySql('delete from orders')).toThrow(KtxQueryError);
|
||||
expect(() => assertReadOnlySql('describe orders')).toThrow(KtxQueryError);
|
||||
expect(() => assertReadOnlySql('select 1; drop table orders')).toThrow(KtxQueryError);
|
||||
try {
|
||||
assertReadOnlySql('describe orders');
|
||||
expect.unreachable('expected a throw');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(KtxExpectedError);
|
||||
}
|
||||
});
|
||||
|
||||
it('accepts read-only queries that begin with leading comments', () => {
|
||||
expect(assertReadOnlySql('-- daily widget sales\nselect count(*) from public.widget_sales')).toBe(
|
||||
'select count(*) from public.widget_sales',
|
||||
|
|
|
|||
|
|
@ -287,6 +287,35 @@ describe('createKtxMcpServer', () => {
|
|||
expect(io.stderrText()).not.toContain('mcpClientVersion');
|
||||
});
|
||||
|
||||
it('records the failure message as errorDetail when a tool returns an error', async () => {
|
||||
vi.spyOn(Math, 'random').mockReturnValue(0);
|
||||
vi.stubEnv('KTX_TELEMETRY_DEBUG', '1');
|
||||
vi.stubEnv('CI', '');
|
||||
const fake = makeFakeServer();
|
||||
const io = makeIo();
|
||||
|
||||
createKtxMcpServer({
|
||||
server: fake.server,
|
||||
userContext: { userId: 'local-user' },
|
||||
projectDir: '/tmp/ktx-mcp-error-detail',
|
||||
io,
|
||||
contextTools: {
|
||||
knowledge: {
|
||||
search: vi.fn<KtxKnowledgeMcpPort['search']>().mockRejectedValue(new Error('wiki search exploded')),
|
||||
read: vi.fn<KtxKnowledgeMcpPort['read']>().mockResolvedValue(null),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(getTool(fake.tools, 'wiki_search').handler({ query: 'revenue', limit: 5 })).resolves.toMatchObject({
|
||||
isError: true,
|
||||
});
|
||||
|
||||
expect(io.stderrText()).toContain('"event":"mcp_request_completed"');
|
||||
expect(io.stderrText()).toContain('"outcome":"error"');
|
||||
expect(io.stderrText()).toContain('"errorDetail":"wiki search exploded"');
|
||||
});
|
||||
|
||||
it('reports MCP tool exceptions with a tool-derived source', async () => {
|
||||
reportExceptionMock.mockClear();
|
||||
vi.stubEnv('ANTHROPIC_API_KEY', 'mcp-anthropic-secret'); // pragma: allowlist secret
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue