Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/alliance/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function setupAllianceServer(config?: ServerConfig): Promise<McpSer
instructions: ALLIANCE_SERVER_INSTRUCTIONS,
});
const ctx = getDefaultServerContext();
registerBuiltinUrlGenerators();
registerBuiltinUrlGenerators(ctx);
registerSuggestQueryParamsTool(server, ctx);
registerGuidedQueryTool(server, ctx);
return server;
Expand Down
157 changes: 157 additions & 0 deletions src/alliance/tools/guided-query-tool.context.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,35 @@
import { describe, expect, it, vi } from 'vitest';
import { registerBuiltinUrlGenerators } from '../url-builtins.js';
import { registerQueryTool } from '../../core/server/tools/query-tool.js';
import { registerSuggestQueryParamsTool } from './suggest-query-params-tool.js';
import { registerGuidedQueryTool } from './guided-query-tool.js';
import {
assertToolErrorCode,
createMockServer,
createTestServerContext,
makeHybridQueryResult,
makeSearchResult,
parseToolJson,
} from '../../core/server/tools/test-helpers.js';

const namespaceMetadata = {
document_number: 'string',
title: 'string',
url: 'string',
author: 'string',
chunk_text: 'string',
};

function papersNamespaceClient(overrides?: { query?: ReturnType<typeof vi.fn> }) {
return {
listNamespacesWithMetadata: vi.fn().mockResolvedValue([
{ namespace: 'papers', recordCount: 42, metadata: namespaceMetadata },
]),
query: overrides?.query ?? vi.fn().mockResolvedValue(makeHybridQueryResult()),
count: vi.fn().mockResolvedValue({ count: 7, truncated: false }),
};
}

describe('guided_query tool handler (ServerContext instance path)', () => {
it('returns success with decision_trace using injected context', async () => {
const listNamespacesWithMetadata = vi.fn().mockResolvedValue([
Expand Down Expand Up @@ -53,4 +76,138 @@ describe('guided_query tool handler (ServerContext instance path)', () => {
expect(trace['rerank_status']).toBeDefined();
expect(query).toHaveBeenCalledOnce();
});

it('surfaces degraded and hybrid_leg_failed in result', async () => {
const query = vi.fn().mockResolvedValue(
makeHybridQueryResult({
degraded: true,
degradation_reason: 'sparse_leg_empty',
hybrid_leg_failed: 'sparse',
})
);
const ctx = createTestServerContext({
client: papersNamespaceClient({ query }) as never,
});
const server = createMockServer();
registerGuidedQueryTool(server as never, ctx);
const body = parseToolJson(
await server.getHandler('guided_query')!({
user_query: 'contracts',
namespace: 'papers',
preferred_tool: 'fast',
enrich_urls: false,
})
);
const result = body['result'] as Record<string, unknown>;
expect(result['degraded']).toBe(true);
expect(result['hybrid_leg_failed']).toBe('sparse');
expect(result['degradation_reason']).toBe('sparse_leg_empty');
});

it('enriches urls via ctx builtins when enrich_urls is true', async () => {
const mailingDocId =
'boost-announce@lists.boost.org/message/O5VYCDZADVDHK5Z5LAYJBHMDOAFQL7P6';
const query = vi.fn().mockResolvedValue(
makeHybridQueryResult({
results: [
makeSearchResult({
metadata: {
document_number: 'MSG-1',
title: 'T',
author: 'A',
doc_id: mailingDocId,
},
}),
],
})
);
const ctx = createTestServerContext({
client: {
listNamespacesWithMetadata: vi.fn().mockResolvedValue([
{
namespace: 'mailing',
recordCount: 42,
metadata: {
document_number: 'string',
title: 'string',
author: 'string',
chunk_text: 'string',
},
},
]),
query,
count: vi.fn(),
} as never,
});
registerBuiltinUrlGenerators(ctx);
const server = createMockServer();
registerGuidedQueryTool(server as never, ctx);
const body = parseToolJson(
await server.getHandler('guided_query')!({
user_query: 'announcement',
namespace: 'mailing',
preferred_tool: 'fast',
enrich_urls: true,
})
);
const result = body['result'] as Record<string, unknown>;
const rows = result['results'] as Array<{ url: string }>;
expect(rows[0]?.url).toContain('lists.boost.org');
});

it('returns TIMEOUT when orchestrator client throws timeout error', async () => {
const query = vi.fn().mockRejectedValue(
new Error('Timeout after 5000ms while waiting for query')
);
const ctx = createTestServerContext({
client: papersNamespaceClient({ query }) as never,
});
const server = createMockServer();
registerGuidedQueryTool(server as never, ctx);
const err = assertToolErrorCode(
await server.getHandler('guided_query')!({
user_query: 'contracts',
namespace: 'papers',
preferred_tool: 'fast',
enrich_urls: false,
}),
'TIMEOUT'
);
expect(err.suggestion).toMatch(/retry|timeout/i);
});

it('does not block explicit suggest_query_params after internal suggest', async () => {
const ctx = createTestServerContext({
client: papersNamespaceClient() as never,
});
const guidedServer = createMockServer();
registerGuidedQueryTool(guidedServer as never, ctx);
await guidedServer.getHandler('guided_query')!({
user_query: 'What does the paper say?',
namespace: 'papers',
preferred_tool: 'fast',
enrich_urls: false,
});

const suggestServer = createMockServer();
registerSuggestQueryParamsTool(suggestServer as never, ctx);
const suggestBody = parseToolJson(
await suggestServer.getHandler('suggest_query_params')!({
namespace: 'papers',
user_query: 'List titles',
})
);
expect(suggestBody['status']).toBe('success');

const queryServer = createMockServer();
registerQueryTool(queryServer as never, ctx);
const queryBody = parseToolJson(
await queryServer.getHandler('query')!({
query_text: 'List titles',
namespace: 'papers',
preset: 'fast',
})
);
expect(queryBody['status']).toBe('success');
});
});
1 change: 1 addition & 0 deletions src/alliance/tools/guided-query-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ export function registerGuidedQueryTool(server: McpServer, ctx?: ServerContext):
const formattedResults = formatQueryResultRows(queryOutcome.results, {
namespace,
enrichUrls: enrich_urls,
ctx,
});
const result: QueryResponse = {
status: 'success',
Expand Down
77 changes: 77 additions & 0 deletions src/alliance/tools/isolated-context.context.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { registerBuiltinUrlGenerators } from '../url-builtins.js';
import { registerGuidedQueryTool } from './guided-query-tool.js';
import {
createMockServer,
createTestServerContext,
isolateFromDefaultContext,
makeHybridQueryResult,
makeSearchResult,
parseToolJson,
} from '../../core/server/tools/test-helpers.js';
import { getDefaultServerContext, teardownDefaultServerContext } from '../../core/server/server-context.js';

const MAILING_DOC_ID =
'boost-announce@lists.boost.org/message/O5VYCDZADVDHK5Z5LAYJBHMDOAFQL7P6';
const EXPECTED_MAILING_URL = `https://lists.boost.org/archives/list/${MAILING_DOC_ID}/`;

describe('isolated ServerContext with zero default context', () => {
beforeEach(() => {
isolateFromDefaultContext();
});

afterEach(() => {
teardownDefaultServerContext();
});

it('guided_query enrich_urls uses ctx builtins, not default registry', async () => {
const listNamespacesWithMetadata = vi.fn().mockResolvedValue([
{
namespace: 'mailing',
recordCount: 42,
metadata: {
document_number: 'string',
title: 'string',
author: 'string',
chunk_text: 'string',
},
},
]);
const query = vi.fn().mockResolvedValue(
makeHybridQueryResult({
results: [
makeSearchResult({
metadata: {
document_number: 'MSG-1',
title: 'T',
author: 'A',
doc_id: MAILING_DOC_ID,
},
}),
],
})
);
const ctx = createTestServerContext({
client: {
listNamespacesWithMetadata,
query,
count: vi.fn(),
} as never,
});
registerBuiltinUrlGenerators(ctx);

const server = createMockServer();
registerGuidedQueryTool(server as never, ctx);
const raw = await server.getHandler('guided_query')!({
user_query: 'What was announced?',
namespace: 'mailing',
preferred_tool: 'fast',
enrich_urls: true,
});
const body = parseToolJson(raw);
const result = body['result'] as Record<string, unknown>;
const rows = result['results'] as Array<{ url: string }>;
expect(rows[0]?.url).toBe(EXPECTED_MAILING_URL);
expect(getDefaultServerContext().hasUrlGenerator('mailing')).toBe(false);
});
});
75 changes: 74 additions & 1 deletion src/alliance/tools/suggest-query-params-tool.context.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,34 @@
import { describe, expect, it, vi } from 'vitest';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { registerQueryTool } from '../../core/server/tools/query-tool.js';
import { registerSuggestQueryParamsTool } from './suggest-query-params-tool.js';
import {
createMockServer,
createTestServerContext,
makeHybridQueryResult,
parseToolJson,
} from '../../core/server/tools/test-helpers.js';

const namespaceMetadata = {
document_number: 'string',
title: 'string',
url: 'string',
author: 'string',
chunk_text: 'string',
};

function mockNamespacesClient() {
return {
listNamespacesWithMetadata: vi.fn().mockResolvedValue([
{ namespace: 'wg21', recordCount: 42, metadata: namespaceMetadata },
]),
};
}

describe('suggest_query_params tool handler (ServerContext instance path)', () => {
afterEach(() => {
vi.useRealTimers();
});

it('marks suggest-flow on injected context when namespace exists', async () => {
const listNamespacesWithMetadata = vi.fn().mockResolvedValue([
{
Expand Down Expand Up @@ -41,4 +63,55 @@ describe('suggest_query_params tool handler (ServerContext instance path)', () =
const flowCheck = ctx.requireSuggested('wg21');
expect(flowCheck.ok).toBe(true);
});

it('updates suggestion state when called twice for the same namespace', async () => {
const ctx = createTestServerContext({
client: mockNamespacesClient() as never,
});
const server = createMockServer();
registerSuggestQueryParamsTool(server as never, ctx);
const handler = server.getHandler('suggest_query_params')!;

await handler({ namespace: 'wg21', user_query: 'List papers with titles' });
await handler({ namespace: 'wg21', user_query: 'how many records match?' });

const flowCheck = ctx.requireSuggested('wg21');
expect(flowCheck.ok).toBe(true);
if (flowCheck.ok) {
expect(flowCheck.flow.user_query).toBe('how many records match?');
expect(flowCheck.flow.recommended_tool).toBe('count');
}
});

it('re-suggest after expiry allows query without FLOW_GATE', async () => {
vi.useFakeTimers();
const query = vi.fn().mockResolvedValue(makeHybridQueryResult());
const ctx = createTestServerContext({
config: { cacheTtlSeconds: 1 },
client: { ...mockNamespacesClient(), query } as never,
});
const suggestServer = createMockServer();
registerSuggestQueryParamsTool(suggestServer as never, ctx);
const suggestHandler = suggestServer.getHandler('suggest_query_params')!;

await suggestHandler({ namespace: 'wg21', user_query: 'contracts' });
vi.advanceTimersByTime(2000);

const reSuggestBody = parseToolJson(
await suggestHandler({ namespace: 'wg21', user_query: 'contracts again' })
);
expect(reSuggestBody['status']).toBe('success');

const queryServer = createMockServer();
registerQueryTool(queryServer as never, ctx);
const queryBody = parseToolJson(
await queryServer.getHandler('query')!({
query_text: 'contracts again',
namespace: 'wg21',
preset: 'fast',
})
);
expect(queryBody['status']).toBe('success');
expect(query).toHaveBeenCalledOnce();
});
});
23 changes: 23 additions & 0 deletions src/alliance/url-builtins.context.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, expect, it } from 'vitest';
import { registerBuiltinUrlGenerators } from './url-builtins.js';
import { createTestServerContext } from '../core/server/tools/test-helpers.js';

const MAILING_DOC_ID =
'boost-announce@lists.boost.org/message/O5VYCDZADVDHK5Z5LAYJBHMDOAFQL7P6';

describe('registerBuiltinUrlGenerators (ServerContext instance path)', () => {
it('registers builtins only on the target context', () => {
const ctxA = createTestServerContext();
const ctxB = createTestServerContext();
registerBuiltinUrlGenerators(ctxA);

const metadata = { doc_id: MAILING_DOC_ID };
const fromA = ctxA.generateUrlForNamespace('mailing', metadata);
const fromB = ctxB.generateUrlForNamespace('mailing', metadata);

expect(fromA.url).toContain('lists.boost.org');
expect(fromA.method).toBe('generated.mailing');
expect(fromB.method).toBe('unavailable');
expect(fromB.url).toBeNull();
});
});
Loading
Loading