Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
156 changes: 156 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,137 @@ 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
79 changes: 79 additions & 0 deletions src/alliance/tools/isolated-context.context.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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();
});
});
22 changes: 22 additions & 0 deletions src/alliance/url-builtins.context.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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