Skip to content

Commit e81bbc5

Browse files
author
zho
committed
#15-fix for review
1 parent eb593e9 commit e81bbc5

3 files changed

Lines changed: 57 additions & 12 deletions

File tree

src/server/tools/count-tool.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ export function registerCountTool(server: McpServer): void {
2323
'count',
2424
{
2525
description:
26-
'Get the count of unique documents matching a metadata filter and semantic query. ' +
27-
'Use when the user asks for a count (e.g. "how many papers by Lakos?", "Lakos\'s paper count"). ' +
26+
'Get the number of unique documents matching a metadata filter and semantic query. ' +
27+
'Use when the user asks for a count (e.g. "how many papers by J. Lakos?", "John\'s paper count"). ' +
2828
'Uses semantic (dense) search only and requests only document identifiers (no content) for performance. ' +
29-
'Returns unique document count (deduped by document_number/url/doc_id) up to 10,000; truncated=true if at least that many. ' +
29+
'Returns the number of unique documents (deduped by document_number/url/doc_id) up to 10,000; truncated=true if at least that many. ' +
3030
'Mandatory flow: call suggest_query_params first, then count. ' +
3131
'Use list_namespaces to discover namespace and metadata fields. ' +
3232
'For count-by-metadata only, use a broad query_text (e.g. "paper" or "document"). ' +
@@ -43,7 +43,7 @@ export function registerCountTool(server: McpServer): void {
4343
metadata_filter: metadataFilterSchema
4444
.optional()
4545
.describe(
46-
'Optional metadata filter. Use exact field names from list_namespaces. E.g. {"author": {"$in": ["John Lakos"]}} for author count.'
46+
'Optional metadata filter. Use exact field names from list_namespaces. E.g. {"author": {"$in": ["John Lakos", "J. Lakos"]}} for author count.'
4747
),
4848
},
4949
},

src/server/url-generation.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,39 @@ describe('generateUrlForNamespace', () => {
3131
expect(r.method).toBe('generated.mailing');
3232
});
3333

34+
it('generates mailing URL as list_name/message/doc_id when list_name present and doc_id does not contain it', () => {
35+
const r = generateUrlForNamespace('mailing', {
36+
list_name: 'boost-users',
37+
doc_id: '12345',
38+
});
39+
expect(r.url).toBe(
40+
'https://lists.boost.org/archives/list/boost-users/message/12345/'
41+
);
42+
expect(r.method).toBe('generated.mailing');
43+
});
44+
45+
it('uses msg_id when list_name present and doc_id missing', () => {
46+
const r = generateUrlForNamespace('mailing', {
47+
list_name: 'boost-announce',
48+
msg_id: '67890',
49+
});
50+
expect(r.url).toBe(
51+
'https://lists.boost.org/archives/list/boost-announce/message/67890/'
52+
);
53+
expect(r.method).toBe('generated.mailing');
54+
});
55+
56+
it('uses single-path form when doc_id contains list_name (no list_name/message split)', () => {
57+
const r = generateUrlForNamespace('mailing', {
58+
list_name: 'boost-users',
59+
doc_id: 'boost-users@lists.boost.org/message/12345',
60+
});
61+
expect(r.url).toBe(
62+
'https://lists.boost.org/archives/list/boost-users@lists.boost.org/message/12345/'
63+
);
64+
expect(r.method).toBe('generated.mailing');
65+
});
66+
3467
it('uses slack source when available', () => {
3568
const r = generateUrlForNamespace('slack-Cpplang', {
3669
source: 'https://app.slack.com/client/T123/C123/p123',

src/server/url-generation.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,31 @@ function asString(value: unknown): string | null {
1919
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
2020
}
2121

22-
/** Build a mailing-list URL from doc_id or thread_id (e.g. Boost archives). */
22+
/**
23+
* Build a mailing-list URL (e.g. Boost archives).
24+
* Two cases:
25+
* 1. If metadata has list_name and doc_id (or msg_id) and the message id does not contain list_name,
26+
* URL is https://lists.boost.org/archives/list/{list_name}/message/{doc_id}/
27+
* 2. Otherwise use doc_id or thread_id as the list path: .../list/{doc_id_or_thread_id}/
28+
*/
2329
function generatorMailing(metadata: Record<string, unknown>): UrlGenerationResult {
24-
const docIdOrThread = asString(metadata['doc_id']) ?? asString(metadata['thread_id']);
30+
const listName = asString(metadata['list_name']);
31+
const docId = asString(metadata['doc_id']) ?? asString(metadata['msg_id']);
32+
const threadId = asString(metadata['thread_id']);
33+
34+
if (listName && docId && !docId.includes(listName)) {
35+
return {
36+
url: `https://lists.boost.org/archives/list/${listName}/message/${docId}/`,
37+
method: 'generated.mailing',
38+
};
39+
}
40+
41+
const docIdOrThread = docId ?? threadId;
2542
if (!docIdOrThread) {
2643
return {
2744
url: null,
2845
method: 'unavailable',
29-
reason: 'mailing requires doc_id or thread_id to generate URL',
46+
reason: 'mailing requires doc_id, msg_id, or thread_id to generate URL',
3047
};
3148
}
3249
return {
@@ -85,8 +102,3 @@ export function generateUrlForNamespace(
85102
reason: `URL generation is not supported for namespace "${namespace}"`,
86103
};
87104
}
88-
89-
/** Register a URL generator for a namespace (e.g. for custom indexes). */
90-
export function registerUrlGenerator(namespace: string, fn: UrlGenerator): void {
91-
urlGenerators.set(namespace, fn);
92-
}

0 commit comments

Comments
 (0)