Skip to content

Commit e5cb8d1

Browse files
committed
fix(search): remove exactMatch flag from MCP API, improve internal handling
- remove exactMatch flag from the MCP server interface to prevent confusion for LLM/API users - keep exactMatch logic for CLI/internal use, now robustly throws VersionNotFoundError with available versions if used without a version - add and update unit tests for all exactMatch scenarios resolves #24
1 parent eef1c96 commit e5cb8d1

File tree

3 files changed

+54
-12
lines changed

3 files changed

+54
-12
lines changed

src/mcp/index.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@ export async function startServer() {
135135
"Search indexed documentation. Examples:\n" +
136136
'- {library: "react", query: "how do hooks work"} -> matches latest version of React\n' +
137137
'- {library: "react", version: "18.0.0", query: "how do hooks work"} -> matches React 18.0.0 or earlier\n' +
138-
'- {library: "react", version: "18.0.0", query: "how do hooks work", exactMatch: true} -> only React 18.0.0\n' +
139138
'- {library: "typescript", version: "5.x", query: "ReturnType example"} -> any TypeScript 5.x.x version\n' +
140139
'- {library: "typescript", version: "5.2.x", query: "ReturnType example"} -> any TypeScript 5.2.x version',
141140
{
@@ -148,20 +147,15 @@ export async function startServer() {
148147
),
149148
query: z.string().describe("Search query"),
150149
limit: z.number().optional().default(5).describe("Maximum number of results"),
151-
exactMatch: z
152-
.boolean()
153-
.optional()
154-
.default(false)
155-
.describe("Only use exact version match"),
156150
},
157-
async ({ library, version, query, limit, exactMatch }) => {
151+
async ({ library, version, query, limit }) => {
158152
try {
159153
const result = await tools.search.execute({
160154
library,
161155
version,
162156
query,
163157
limit,
164-
exactMatch,
158+
exactMatch: false, // Always false for MCP interface
165159
});
166160

167161
const formattedResults = result.results.map(

src/tools/SearchTool.test.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe("SearchTool", () => {
2020
validateLibraryExists: vi.fn(),
2121
findBestVersion: vi.fn(),
2222
searchStore: vi.fn(),
23+
listVersions: vi.fn(),
2324
};
2425

2526
searchTool = new SearchTool(mockDocService as DocumentManagementService);
@@ -58,6 +59,37 @@ describe("SearchTool", () => {
5859
expect(result.error).toBeUndefined();
5960
});
6061

62+
it("should throw VersionNotFoundError when exactMatch is true but no version is specified", async () => {
63+
const options: SearchToolOptions = {
64+
...baseOptions,
65+
exactMatch: true,
66+
};
67+
const availableVersions = [{ version: "1.0.0", indexed: true }];
68+
(mockDocService.validateLibraryExists as Mock).mockResolvedValue(undefined);
69+
(mockDocService.listVersions as Mock).mockResolvedValue(availableVersions);
70+
71+
await expect(searchTool.execute(options)).rejects.toThrow(VersionNotFoundError);
72+
expect(mockDocService.validateLibraryExists).toHaveBeenCalledWith("test-lib");
73+
expect(mockDocService.listVersions).toHaveBeenCalledWith("test-lib");
74+
expect(mockDocService.searchStore).not.toHaveBeenCalled();
75+
});
76+
77+
it("should throw VersionNotFoundError when exactMatch is true with 'latest' version", async () => {
78+
const options: SearchToolOptions = {
79+
...baseOptions,
80+
version: "latest",
81+
exactMatch: true,
82+
};
83+
const availableVersions = [{ version: "1.0.0", indexed: true }];
84+
(mockDocService.validateLibraryExists as Mock).mockResolvedValue(undefined);
85+
(mockDocService.listVersions as Mock).mockResolvedValue(availableVersions);
86+
87+
await expect(searchTool.execute(options)).rejects.toThrow(VersionNotFoundError);
88+
expect(mockDocService.validateLibraryExists).toHaveBeenCalledWith("test-lib");
89+
expect(mockDocService.listVersions).toHaveBeenCalledWith("test-lib");
90+
expect(mockDocService.searchStore).not.toHaveBeenCalled();
91+
});
92+
6193
it("should find best version and search when exactMatch is false (default)", async () => {
6294
const options: SearchToolOptions = { ...baseOptions, version: "1.x" };
6395
const findVersionResult = { bestMatch: "1.2.0", hasUnversioned: false };
@@ -105,7 +137,8 @@ describe("SearchTool", () => {
105137

106138
await searchTool.execute(options);
107139

108-
expect(mockDocService.findBestVersion).toHaveBeenCalledWith("test-lib", "latest"); // Default version
140+
// The implementation passes undefined, which is defaulted to "latest" in the method
141+
expect(mockDocService.findBestVersion).toHaveBeenCalledWith("test-lib", undefined);
109142
expect(mockDocService.searchStore).toHaveBeenCalledWith(
110143
"test-lib",
111144
"1.2.0",

src/tools/SearchTool.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,33 @@ export class SearchTool {
3535
}
3636

3737
async execute(options: SearchToolOptions): Promise<SearchToolResult> {
38-
const { library, version = "latest", query, limit = 5, exactMatch = false } = options;
38+
const { library, version, query, limit = 5, exactMatch = false } = options;
39+
40+
// When exactMatch is true, version must be specified and not 'latest'
41+
if (exactMatch && (!version || version === "latest")) {
42+
// Get available versions for error message
43+
await this.docService.validateLibraryExists(library);
44+
const versions = await this.docService.listVersions(library);
45+
throw new VersionNotFoundError(
46+
library,
47+
"latest",
48+
versions, // versions already has the correct { version: string, indexed: boolean } format
49+
);
50+
}
51+
52+
// Default to 'latest' only when exactMatch is false
53+
const resolvedVersion = version || "latest";
3954

4055
logger.info(
41-
`🔍 Searching ${library}@${version} for: ${query}${exactMatch ? " (exact match)" : ""}`,
56+
`🔍 Searching ${library}@${resolvedVersion} for: ${query}${exactMatch ? " (exact match)" : ""}`,
4257
);
4358

4459
try {
4560
// 1. Validate library exists first
4661
await this.docService.validateLibraryExists(library);
4762

4863
// 2. Proceed with version finding and searching
49-
let versionToSearch: string | null | undefined = version;
64+
let versionToSearch: string | null | undefined = resolvedVersion;
5065

5166
if (!exactMatch) {
5267
// If not exact match, find the best version (which might be null)

0 commit comments

Comments
 (0)