Skip to content

Commit 687a9a6

Browse files
jonathanMLDevzho
andauthored
update 9 tools (cppalliance#132)
Co-authored-by: zho <jornathanm910923@gmail.com>
1 parent ccff5c9 commit 687a9a6

14 files changed

Lines changed: 338 additions & 45 deletions

src/alliance/setup.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
22
import { ALLIANCE_SERVER_INSTRUCTIONS } from '../constants.js';
33
import type { ServerConfig } from '../core/config.js';
4+
import { getDefaultServerContext } from '../core/server/server-context.js';
45
import { resolveAllianceConfig } from './config.js';
56
import { setupCoreServer } from '../core/setup.js';
67
import { registerBuiltinUrlGenerators } from './url-builtins.js';
@@ -17,8 +18,9 @@ export async function setupAllianceServer(config?: ServerConfig): Promise<McpSer
1718
const server = await setupCoreServer(config ?? resolveAllianceConfig({}), {
1819
instructions: ALLIANCE_SERVER_INSTRUCTIONS,
1920
});
21+
const ctx = getDefaultServerContext();
2022
registerBuiltinUrlGenerators();
21-
registerSuggestQueryParamsTool(server);
22-
registerGuidedQueryTool(server);
23+
registerSuggestQueryParamsTool(server, ctx);
24+
registerGuidedQueryTool(server, ctx);
2325
return server;
2426
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
import { registerGuidedQueryTool } from './guided-query-tool.js';
3+
import {
4+
createMockServer,
5+
createTestServerContext,
6+
makeHybridQueryResult,
7+
parseToolJson,
8+
} from '../../core/server/tools/test-helpers.js';
9+
10+
describe('guided_query tool handler (ServerContext instance path)', () => {
11+
it('returns success with decision_trace using injected context', async () => {
12+
const listNamespacesWithMetadata = vi.fn().mockResolvedValue([
13+
{
14+
namespace: 'papers',
15+
recordCount: 42,
16+
metadata: {
17+
document_number: 'string',
18+
title: 'string',
19+
url: 'string',
20+
author: 'string',
21+
chunk_text: 'string',
22+
},
23+
},
24+
]);
25+
const query = vi.fn().mockResolvedValue(makeHybridQueryResult());
26+
const ctx = createTestServerContext({
27+
client: {
28+
listNamespacesWithMetadata,
29+
query,
30+
count: vi.fn().mockResolvedValue({ count: 7, truncated: false }),
31+
} as never,
32+
});
33+
34+
const server = createMockServer();
35+
registerGuidedQueryTool(server as never, ctx);
36+
const raw = await server.getHandler('guided_query')!({
37+
user_query: 'What does the paper say about contracts?',
38+
namespace: 'papers',
39+
top_k: 8,
40+
preferred_tool: 'auto',
41+
enrich_urls: false,
42+
});
43+
const body = parseToolJson(raw);
44+
expect(body).toMatchObject({
45+
status: 'success',
46+
});
47+
const trace = body['decision_trace'] as Record<string, unknown>;
48+
expect(trace).toMatchObject({
49+
cache_hit: false,
50+
selected_namespace: 'papers',
51+
enrich_urls: false,
52+
});
53+
expect(trace['rerank_status']).toBeDefined();
54+
expect(query).toHaveBeenCalledOnce();
55+
});
56+
});

src/alliance/tools/guided-query-tool.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import { rankNamespacesByQuery } from '../../core/server/namespace-router.js';
1313
import { getNamespacesWithCache } from '../../core/server/namespaces-cache.js';
1414
import { normalizeNamespace } from '../../core/server/namespace-utils.js';
15+
import type { ServerContext } from '../../core/server/server-context.js';
1516
import { suggestQueryParams } from '../../core/server/query-suggestion.js';
1617
import { markSuggested } from '../../core/server/suggestion-flow.js';
1718
import {
@@ -39,7 +40,7 @@ function resolveGuidedToolName(
3940
* Registers `guided_query` (routing + suggestion + execution in one call).
4041
* See "Retrieval tool decision matrix" in README.md for tool-selection guidance.
4142
*/
42-
export function registerGuidedQueryTool(server: McpServer): void {
43+
export function registerGuidedQueryTool(server: McpServer, ctx?: ServerContext): void {
4344
server.registerTool(
4445
'guided_query',
4546
{
@@ -102,7 +103,9 @@ export function registerGuidedQueryTool(server: McpServer): void {
102103
}
103104

104105
const queryText = user_query.trim();
105-
const { data: namespaces, cache_hit } = await getNamespacesWithCache();
106+
const { data: namespaces, cache_hit } = ctx
107+
? await ctx.getNamespacesWithCache()
108+
: await getNamespacesWithCache();
106109
const ranked = rankNamespacesByQuery(queryText, namespaces, 3);
107110

108111
let namespace: string | null = null;
@@ -150,13 +153,21 @@ export function registerGuidedQueryTool(server: McpServer): void {
150153
}
151154

152155
const selectedTool: GuidedToolName = resolveGuidedToolName(preferred_tool, suggestion);
153-
markSuggested(namespace, {
154-
recommended_tool: selectedTool,
155-
suggested_fields: suggestion.suggested_fields,
156-
user_query: queryText,
157-
});
156+
if (ctx) {
157+
ctx.markSuggested(namespace, {
158+
recommended_tool: selectedTool,
159+
suggested_fields: suggestion.suggested_fields,
160+
user_query: queryText,
161+
});
162+
} else {
163+
markSuggested(namespace, {
164+
recommended_tool: selectedTool,
165+
suggested_fields: suggestion.suggested_fields,
166+
user_query: queryText,
167+
});
168+
}
158169

159-
const client = getPineconeClient();
170+
const client = ctx ? ctx.getClient() : getPineconeClient();
160171
const baseTrace = {
161172
cache_hit,
162173
input_namespace: inputNamespace ?? null,
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
import { registerSuggestQueryParamsTool } from './suggest-query-params-tool.js';
3+
import {
4+
createMockServer,
5+
createTestServerContext,
6+
parseToolJson,
7+
} from '../../core/server/tools/test-helpers.js';
8+
9+
describe('suggest_query_params tool handler (ServerContext instance path)', () => {
10+
it('marks suggest-flow on injected context when namespace exists', async () => {
11+
const listNamespacesWithMetadata = vi.fn().mockResolvedValue([
12+
{
13+
namespace: 'wg21',
14+
recordCount: 42,
15+
metadata: {
16+
document_number: 'string',
17+
title: 'string',
18+
url: 'string',
19+
author: 'string',
20+
chunk_text: 'string',
21+
},
22+
},
23+
]);
24+
const ctx = createTestServerContext({
25+
client: { listNamespacesWithMetadata } as never,
26+
});
27+
28+
const server = createMockServer();
29+
registerSuggestQueryParamsTool(server as never, ctx);
30+
const raw = await server.getHandler('suggest_query_params')!({
31+
namespace: 'wg21',
32+
user_query: 'List papers with titles',
33+
});
34+
const body = parseToolJson(raw);
35+
expect(body).toMatchObject({
36+
status: 'success',
37+
namespace_found: true,
38+
cache_hit: false,
39+
});
40+
41+
const flowCheck = ctx.requireSuggested('wg21');
42+
expect(flowCheck.ok).toBe(true);
43+
});
44+
});

src/alliance/tools/suggest-query-params-tool.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { z } from 'zod';
33
import { normalizeNamespace } from '../../core/server/namespace-utils.js';
44
import { getNamespacesWithCache } from '../../core/server/namespaces-cache.js';
55
import { suggestQueryParams } from '../../core/server/query-suggestion.js';
6+
import type { ServerContext } from '../../core/server/server-context.js';
67
import { markSuggested } from '../../core/server/suggestion-flow.js';
78
import {
89
classifyToolCatchError,
@@ -12,7 +13,7 @@ import {
1213
import { jsonErrorResponse, jsonResponse } from '../../core/server/tool-response.js';
1314

1415
/** Register the suggest_query_params tool on the MCP server. */
15-
export function registerSuggestQueryParamsTool(server: McpServer): void {
16+
export function registerSuggestQueryParamsTool(server: McpServer, ctx?: ServerContext): void {
1617
server.registerTool(
1718
'suggest_query_params',
1819
{
@@ -48,18 +49,28 @@ export function registerSuggestQueryParamsTool(server: McpServer): void {
4849
})
4950
);
5051
}
51-
const { data: namespacesInfo, cache_hit } = await getNamespacesWithCache();
52+
const { data: namespacesInfo, cache_hit } = ctx
53+
? await ctx.getNamespacesWithCache()
54+
: await getNamespacesWithCache();
5255
const ns = namespacesInfo.find(
5356
(n) => n.namespace === nsNorm || normalizeNamespace(n.namespace) === nsNorm
5457
);
5558
const metadataFields = ns?.metadata ?? null;
5659
const result = suggestQueryParams(metadataFields, user_query.trim());
5760
if (result.namespace_found) {
58-
markSuggested(nsNorm, {
59-
recommended_tool: result.recommended_tool,
60-
suggested_fields: result.suggested_fields,
61-
user_query: user_query.trim(),
62-
});
61+
if (ctx) {
62+
ctx.markSuggested(nsNorm, {
63+
recommended_tool: result.recommended_tool,
64+
suggested_fields: result.suggested_fields,
65+
user_query: user_query.trim(),
66+
});
67+
} else {
68+
markSuggested(nsNorm, {
69+
recommended_tool: result.recommended_tool,
70+
suggested_fields: result.suggested_fields,
71+
user_query: user_query.trim(),
72+
});
73+
}
6374
}
6475
const response = {
6576
...result,
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { registerGenerateUrlsTool } from './generate-urls-tool.js';
3+
import { createMockServer, createTestServerContext, parseToolJson } from './test-helpers.js';
4+
5+
describe('generate_urls tool handler (ServerContext instance path)', () => {
6+
it('uses URL generator registered on injected context', async () => {
7+
const ctx = createTestServerContext();
8+
ctx.registerUrlGenerator('mailing', () => ({
9+
url: 'https://example.com/doc/P1234',
10+
method: 'generator',
11+
}));
12+
13+
const server = createMockServer();
14+
registerGenerateUrlsTool(server as never, ctx);
15+
const raw = await server.getHandler('generate_urls')!({
16+
namespace: 'mailing',
17+
records: [{ document_number: 'P1234' }],
18+
});
19+
const body = parseToolJson(raw);
20+
expect(body).toMatchObject({
21+
status: 'success',
22+
namespace: 'mailing',
23+
count: 1,
24+
});
25+
const results = body['results'] as Array<{ url: string; method: string }>;
26+
expect(results[0]).toMatchObject({
27+
url: 'https://example.com/doc/P1234',
28+
method: 'generator',
29+
});
30+
});
31+
});

src/core/server/tools/generate-urls-tool.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
22
import { z } from 'zod';
3-
import { generateUrlForNamespace } from '../url-registry.js';
43
import { normalizeNamespace } from '../namespace-utils.js';
4+
import type { ServerContext } from '../server-context.js';
5+
import { generateUrlForNamespace } from '../url-registry.js';
56
import { classifyToolCatchError, logToolError, validationToolError } from '../tool-error.js';
67
import { jsonErrorResponse, jsonResponse } from '../tool-response.js';
78

@@ -15,7 +16,7 @@ function extractMetadata(record: Record<string, unknown>): Record<string, unknow
1516
}
1617

1718
/** Register the generate_urls tool on the MCP server. */
18-
export function registerGenerateUrlsTool(server: McpServer): void {
19+
export function registerGenerateUrlsTool(server: McpServer, ctx?: ServerContext): void {
1920
server.registerTool(
2021
'generate_urls',
2122
{
@@ -49,7 +50,9 @@ export function registerGenerateUrlsTool(server: McpServer): void {
4950
}
5051
const results = records.map((record, index) => {
5152
const metadata = extractMetadata(record);
52-
const generated = generateUrlForNamespace(nsNorm, metadata);
53+
const generated = ctx
54+
? ctx.generateUrlForNamespace(nsNorm, metadata)
55+
: generateUrlForNamespace(nsNorm, metadata);
5356
return {
5457
index,
5558
url: generated.url,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
import { registerKeywordSearchTool } from './keyword-search-tool.js';
3+
import {
4+
createMockServer,
5+
createTestServerContext,
6+
makeSearchResult,
7+
parseToolJson,
8+
} from './test-helpers.js';
9+
10+
describe('keyword_search tool handler (ServerContext instance path)', () => {
11+
it('returns success using injected client', async () => {
12+
const keywordSearch = vi.fn().mockResolvedValue([makeSearchResult()]);
13+
const ctx = createTestServerContext({
14+
client: {
15+
keywordSearch,
16+
getSparseIndexName: () => 'test-index-sparse',
17+
} as never,
18+
});
19+
20+
const server = createMockServer();
21+
registerKeywordSearchTool(server as never, ctx);
22+
const raw = await server.getHandler('keyword_search')!({
23+
query_text: 'contracts',
24+
namespace: 'wg21',
25+
top_k: 5,
26+
});
27+
const body = parseToolJson(raw);
28+
expect(body).toMatchObject({
29+
status: 'success',
30+
query: 'contracts',
31+
namespace: 'wg21',
32+
index: 'test-index-sparse',
33+
result_count: 1,
34+
});
35+
expect(keywordSearch).toHaveBeenCalledOnce();
36+
});
37+
});

0 commit comments

Comments
 (0)